يحتاج كل تطبيق ASP.NET تقريبًا إلى تتبع البيانات الخاصة بجلسة المستخدم. يوفر ASP.NET فئة HttpSessionState لتخزين قيم حالة الجلسة. يمكن الوصول إلى مثيل فئة HttpSessionState لكل طلب HTTP عبر التطبيق الخاص بك باستخدام خاصية HttpContext.Current.Session الثابتة. أصبح الوصول إلى نفس المثيل أسهل في كل صفحة وUserControl باستخدام خاصية الجلسة الخاصة بالصفحة أو UserControl.
توفر فئة HttpSessionState مجموعة من أزواج المفاتيح/القيم، حيث تكون المفاتيح من النوع String والقيم من النوع Object. هذا يعني أن الجلسة مرنة للغاية ويمكنك تخزين أي نوع من البيانات تقريبًا في الجلسة.
ولكن (هناك دائمًا لكن) هذه المرونة لا تأتي بدون تكلفة. التكلفة هي السهولة التي يمكن بها إدخال الأخطاء في تطبيقك. لن يتم العثور على العديد من الأخطاء التي يمكن تقديمها عن طريق اختبار الوحدة، وربما ليس عن طريق أي شكل من أشكال الاختبار المنظم. غالبًا ما تظهر هذه الأخطاء فقط عند نشر التطبيق في بيئة الإنتاج. عندما تظهر على السطح، غالبًا ما يكون من الصعب جدًا، إن لم يكن من المستحيل، تحديد كيفية حدوثها والقدرة على إعادة إنتاج الخطأ. هذا يعني أن إصلاحها مكلف للغاية.
تقدم هذه المقالة إستراتيجية للمساعدة في منع هذا النوع من الأخطاء. يستخدم نمط تصميم يسمى الواجهة، حيث أنه يغلف الواجهة المجانية للغاية التي توفرها فئة HttpSessionState (والتي يمكنها تلبية متطلبات أي تطبيق) بواجهة مصممة بشكل جيد ويمكن التحكم فيها ومصممة خصيصًا لتطبيق معين. إذا لم تكن على دراية بأنماط التصميم أو نمط الواجهة، فإن البحث السريع عبر الإنترنت عن "نمط تصميم الواجهة" سيوفر لك الكثير من المعلومات الأساسية. ومع ذلك، ليس عليك أن تفهم أنماط التصميم لكي تفهم هذه المقالة.
رمز المثال الموضح في هذه المقالة مكتوب بلغة C#، لكن المفاهيم قابلة للتطبيق على أي لغة .NET.
ما هي المشكلة؟
في هذا القسم من المقالة سأصف مشاكل الوصول المباشر إلى فئة HttpSessionState، بدون الواجهة. سأصف أنواع الأخطاء التي يمكن إدخالها.
يوضح ما يلي الكود النموذجي المكتوب للوصول إلى متغيرات حالة الجلسة.
// احفظ متغير الجلسة
Session["some string"] = AnyOldObject;
// اقرأ متغير الجلسة
DateTime startDate = (DateTime)Session["StartDate"];
تنشأ المشاكل من الواجهة المرنة التي توفرها HttpSessionState: المفاتيح هي مجرد سلاسل ولا تتم كتابة القيم بقوة.
استخدام سلسلة حرفية كمفاتيح
إذا تم استخدام سلسلة حرفية كمفاتيح، فلن يتم التحقق من قيمة سلسلة المفتاح بواسطة المترجم. من السهل إنشاء قيم جلسة جديدة عن طريق أخطاء كتابية بسيطة.
الجلسة ["المستلمة"] = 27؛...
الجلسة ["المستلمة"] = 32؛
في الكود أعلاه، تم حفظ قيمتين منفصلتين للجلسة.
سيتم تحديد معظم الأخطاء المشابهة من خلال اختبار الوحدة، ولكن ليس دائمًا. قد لا يكون من الواضح دائمًا أن القيمة لم تتغير كما هو متوقع.
يمكننا تجنب هذا النوع من الأخطاء باستخدام الثوابت:
سلسلة const الخاصة المتلقاة = "تم الاستلام"؛...
الجلسة [المستلمة] = 27؛...
الجلسة [المستلمة] = 32؛
لا يوجد فحص النوع
لا يوجد فحص نوعي للقيم المخزنة في متغيرات الجلسة. لا يمكن للمترجم التحقق من صحة ما يتم تخزينه.
خذ بعين الاعتبار الكود التالي:
Session["maxValue"] = 27;...
int maxValue = (int)Session["maxValue"];
في مكان آخر يتم استخدام التعليمة البرمجية التالية لتحديث القيمة.
جلسة ["maxValue"] = 56.7؛
إذا تم تنفيذ التعليمات البرمجية لقراءة متغير الجلسة "maxValue" في متغير maxValue int مرة أخرى، فسيتم طرح InvalidCastException.
سيتم تحديد معظم الأخطاء المشابهة من خلال اختبار الوحدة، ولكن ليس دائمًا.
إعادة استخدام المفتاح دون قصد
حتى عندما نحدد ثوابت في كل صفحة لمفاتيح الجلسة، فمن الممكن استخدام نفس المفتاح عبر الصفحات عن غير قصد. خذ بعين الاعتبار المثال التالي:
رمز في صفحة واحدة:
سلسلة const الخاصة تحرير = "تحرير"؛...
الجلسة[عدل] = صحيح؛
الكود الموجود على الصفحة الثانية، يتم عرضه بعد الصفحة الأولى:
سلسلة const الخاصة تحرير = "تحرير"؛...
إذا ((منطقي) الجلسة[عدل])
{
...
}
رمز في صفحة ثالثة غير ذات صلة:
سلسلة const الخاصة تحرير = "تحرير"؛...
الجلسة[عدل] = خطأ؛
إذا تم عرض الصفحة الثالثة لسبب ما قبل عرض الصفحة الثانية، فقد لا تكون القيمة هي ما كان متوقعًا. من المحتمل أن يستمر تشغيل الكود، لكن النتائج ستكون خاطئة.
عادةً لن يتم اكتشاف هذا الخطأ أثناء الاختبار. يظهر الخطأ فقط عندما يقوم المستخدم بمجموعة معينة من التنقل في الصفحة (أو فتح نافذة متصفح جديدة).
في أسوأ الأحوال، لا أحد يدرك أن الخطأ قد ظهر، وقد ينتهي بنا الأمر إلى تعديل البيانات إلى قيمة غير مقصودة.
إعادة استخدام المفتاح دون قصد - مرة أخرى
في المثال أعلاه، تم تخزين نفس نوع البيانات في متغير الجلسة. نظرًا لعدم وجود فحص لنوع ما يتم تخزينه، يمكن أن تحدث أيضًا مشكلة أنواع البيانات غير المتوافقة.
الكود في صفحة واحدة:
Session["FollowUp"] = "true";
الكود في الصفحة الثانية:
Session["FollowUp"] = 1;
الكود في الصفحة الثالثة:
Session["FollowUp"] = true;
عندما يظهر الخطأ، سيتم طرح InvalidCastException.
عادةً لن يتم اكتشاف هذا الخطأ أثناء الاختبار. يظهر الخطأ فقط عندما يقوم المستخدم بمجموعة معينة من التنقل في الصفحة (أو فتح نافذة متصفح جديدة).
ماذا يمكننا أن نفعل؟
الحل السريع الأول
أول وأبسط شيء يمكننا القيام به هو التأكد من أننا لا نستخدم أبدًا سلسلة حرفية لمفاتيح الجلسة. استخدم دائمًا الثوابت لتجنب الأخطاء البسيطة في الكتابة.
حد سلسلة const الخاص = "الحد"؛ ...
الجلسة [الحد] = 27؛...
الجلسة [الحد] = 32؛
ومع ذلك، عندما يتم تعريف الثوابت محليًا (على سبيل المثال، على مستوى الصفحة)، فقد نستمر في إعادة استخدام نفس المفتاح دون قصد.
حل سريع أفضل
بدلاً من تحديد الثوابت في كل صفحة، قم بتجميع كافة ثوابت مفاتيح الجلسة في مكان واحد وتوفير الوثائق التي ستظهر في Intellisense. يجب أن تشير الوثائق بوضوح إلى الغرض من استخدام متغير الجلسة. على سبيل المثال، حدد فئة لمفاتيح الجلسة فقط:
الفئة الثابتة العامة SessionKeys
{
/// <الملخص>
/// الحد الأقصى...
/// الملخص>
سلسلة const العامة Limit = "limit";
}
...
جلسة[SessionKeys.Limit] = 27;
عندما تحتاج إلى متغير جلسة جديد، إذا اخترت اسمًا تم استخدامه بالفعل، فستعرف ذلك عند إضافة الثابت إلى فئة مفاتيح الجلسة. يمكنك معرفة كيفية استخدامه حاليًا ويمكنك تحديد ما إذا كان يجب عليك استخدام مفتاح مختلف.
ومع ذلك، ما زلنا لا نضمن اتساق نوع البيانات.
طريقة أفضل بكثير - استخدام الواجهة
قم بالوصول إلى HttpSessionState فقط من خلال فئة ثابتة واحدة في تطبيقك - الواجهة. يجب ألا يكون هناك وصول مباشر إلى خاصية الجلسة من داخل التعليمات البرمجية الموجودة على الصفحات أو عناصر التحكم، ولا يوجد وصول مباشر إلى HttpContext.Current.Session إلا من داخل الواجهة
. سيتم عرض جميع متغيرات الجلسة كخصائص لفئة الواجهة.
وهذا له نفس المزايا مثل استخدام فئة واحدة لجميع مفاتيح الجلسة، بالإضافة إلى المزايا التالية:
كتابة قوية لما يتم وضعه في متغيرات الجلسة.
ليست هناك حاجة لإدخال التعليمات البرمجية حيث يتم استخدام متغيرات الجلسة.
جميع مزايا محددات الخصائص للتحقق من صحة ما يتم وضعه في متغيرات الجلسة (أكثر من مجرد الكتابة).
جميع فوائد الحصول على الخاصية عند الوصول إلى متغيرات الجلسة. على سبيل المثال، تهيئة المتغير عند الوصول إليه لأول مرة.
مثال على فئة واجهة الجلسة
فيما يلي مثال لفئة لتنفيذ واجهة الجلسة لتطبيق يسمى MyApplication.
ينهار
/// <الملخص>
/// يوفر MyApplicationSession واجهة لكائن جلسة ASP.NET.
/// يجب أن يكون الوصول إلى متغيرات الجلسة من خلال هذه الفئة.
/// الملخص>
فئة ثابتة عامة MyApplicationSession
{
#منطقة الثوابت الخاصة
//---------------------------------------------------------------- ---------------------
سلسلة const الخاصة userAuthorisation = "UserAuthorisation";
سلسلة const الخاصة teamManagementState = "TeamManagementState";
سلسلة const الخاصة startDate = "StartDate";
سلسلة const الخاصة endDate = "EndDate";
//---------------------------------------------------------------- ---------------------
# المنطقة
# المنطقة الممتلكات العامة
//---------------------------------------------------------------- ---------------------
/// <الملخص>
/// اسم المستخدم هو اسم المجال واسم المستخدم للمستخدم الحالي.
/// الملخص>
اسم المستخدم للسلسلة الثابتة العامة
{
الحصول على { العودة HttpContext.Current.User.Identity.Name؛ }
}
/// <الملخص>
/// يحتوي ترخيص المستخدم على معلومات الترخيص الخاصة بـ
/// المستخدم الحالي
/// الملخص>
تفويض المستخدم العام الثابت
{
يحصل
{
تفويض المستخدم userAuth
= (UserAuthorisation)HttpContext.Current.Session[userAuthorisation];
// تحقق مما إذا كانت صلاحية ترخيص المستخدم قد انتهت أم لا
لو (
userAuth == فارغة ||
(userAuth.Created.AddMinutes(
MyApplication.Settings.Caching.AuthorisationCache.CacheExpiryMinutes))
< التاريخ والوقت الآن
)
{
userAuth = UserAuthorisation.GetUserAuthorisation(Username);
UserAuthorisation = userAuth;
}
إرجاع userAuth;
}
مجموعة خاصة
{
HttpContext.Current.Session[userAuthorisation] = value;
}
}
/// <الملخص>
/// يتم استخدام TeamManagementState لتخزين الحالة الحالية للملف
/// صفحة TeamManagement.aspx.
/// الملخص>
ثابت عام TeamManagementState TeamManagementState
{
يحصل
{
العودة (TeamManagementState)HttpContext.Current.Session[teamManagementState];
}
تعيين
{
HttpContext.Current.Session[teamManagementState] = value;
}
}
/// <الملخص>
/// تاريخ البدء هو أقرب تاريخ يستخدم لتصفية السجلات.
/// الملخص>
تاريخ البدء الثابت العام
{
يحصل
{
إذا (HttpContext.Current.Session[startDate] == فارغ)
إرجاع DateTime.MinValue؛
آخر
إرجاع (DateTime)HttpContext.Current.Session[startDate];
}
تعيين
{
HttpContext.Current.Session[startDate] = value;
}
}
/// <الملخص>
/// تاريخ الانتهاء هو آخر تاريخ يستخدم لتصفية السجلات.
/// الملخص>
التاريخ والوقت العام الثابت
{
يحصل
{
إذا (HttpContext.Current.Session[endDate] == null)
إرجاع DateTime.MaxValue;
آخر
إرجاع (DateTime)HttpContext.Current.Session[endDate];
}
تعيين
{
HttpContext.Current.Session[endDate] = value;
}
}
//---------------------------------------------------------------- ---------------------
# المنطقة الداخلية
}
يوضح الفصل استخدام حروف الخاصية التي يمكنها توفير القيم الافتراضية إذا لم يتم تخزين القيمة بشكل صريح. على سبيل المثال، توفر الخاصية StartDate DateTime.MinValue كقيمة افتراضية.
يوفر مُحضر الخاصية لخاصية UserAuthorisation ذاكرة تخزين مؤقت بسيطة لمثيل فئة UserAuthorisation، مما يضمن تحديث المثيل الموجود في متغيرات الجلسة. تُظهر هذه الخاصية أيضًا استخدام أداة ضبط خاصة، بحيث لا يمكن ضبط القيمة في متغير الجلسة إلا تحت سيطرة فئة الواجهة.
توضح خاصية اسم المستخدم القيمة التي ربما تم تخزينها كمتغير جلسة ولكن لم تعد مخزنة بهذه الطريقة.
يوضح الكود التالي كيف يمكن الوصول إلى متغير الجلسة من خلال الواجهة. لاحظ أنه ليست هناك حاجة للقيام بأي عملية صب في هذا الرمز.
// احفظ متغير الجلسة
MyApplicationSession.StartDate = DateTime.Today.AddDays(-1);
// اقرأ متغير الجلسة
DateTime startDate = MyApplicationSession.StartDate;
فوائد إضافية
من المزايا الإضافية لنمط تصميم الواجهة أنه يخفي التنفيذ الداخلي عن بقية التطبيق. ربما في المستقبل قد تقرر استخدام آلية أخرى لتنفيذ حالة الجلسة، بخلاف فئة ASP.NET HttpSessionState المضمنة. ما عليك سوى تغيير الأجزاء الداخلية للواجهة - ولا تحتاج إلى تغيير أي شيء آخر في بقية التطبيق.
ملخص
يوفر استخدام الواجهة لـ HttpSessionState طريقة أكثر قوة للوصول إلى متغيرات الجلسة. هذه تقنية بسيطة جدًا للتنفيذ، ولكنها ذات فائدة كبيرة.