State Management در ASP. NET 2.0 - بخش 1
State Management در ASP. NET 2.0 - بخش 1
در يك برنامه سنتی ويندوز ، stateبطور اتوماتيك مديريت می گردد . حافظه به حد فراوان يافت می شود و همواره در دسترس است . در برنامه های وب داستان بگونه ای ديگر است . هزاران كاربر ممكن است بطور همزمان برنامه ای مشابه را بر روی كامپيوتری يكسان ( سرويس دهنده وب ) اجراء و هر يك از آنان از طريق پروتكل HTTP( برگرفته شده از Hypertext Transfer Protocol) كه دارای ماهيتی statelessاست با سرويس دهنده وب ارتباط برقرار نمايند . مجموعه شرايط فوق باعث شده است كه نتوان برنامه های وب را با سناريوئی دقيقا" مشابه با برنامه های سنتی ويندوز طراحی و پياده سازی كرد .
هيچگونه فريمورك برنامه نويسی وب ، صرفنظر از ميزان پيشرفته بودن آن ، نمی تواند ماهيت statelessبودن پروتكل HTTPرا تغيير دهد. پس از هر درخواست و پاسخ به آن ، ارتباط منطقی سرويس گيرنده با سرويس دهنده قطع خواهد شد . معماری فوق ، اين اطمينان را ايجاد می نمايد كه برنامه های وب بتوانند به هزاران كاربر بطور همزمان و بدون نگرانی در خصوص حافظه پاسخ دهند . استفاده از روش های مختلف برای ذخيره اطلاعات بين درخواست های متعدد يك كاربر و بازيابی آنها در زمانی كه به آنها نياز است از جمله مشكلات معماری فوق برای پياده كنندگان برنامه های وب محسوب می گردد .
آشنائی و درك مناسب نسبت به محدوديت های state، يكی از مفاهيم كليدی در زمان ايجاد برنامه های وب كارآ و قدرتمند است .
در مجموعه مقالاتی كه در اين خصوص آماده و بتدريج بر روی سايت منتشر خواهد شد به بررسی موارد زير خواهيم پرداخت :
- آشنائی با مفاهيم ، جايگاه و لزوم مديريت stateدر برنامه های وب
- آشنائی با پتانسيل های ارائه شده در ASP. NET 2.0 برای ذخيره سازی و مديريت اطلاعات
- آشنائی با گزينه های متفاوت موجود به منظور مديريت stateنظير View state، Session state، كوكی های سفارشی
- نحوه انتقال اطلاعات از يك صفحه به صفحه ديگر
مديريت stateو مسائل در ارتباط با آن
در يك برنامه سنتی ويندوز ، كاربران با يك برنامه در حال اجراء بطور پيوسته ارتباط برقرار می نمايند . بخشی از حافظه موجود بر روی كامپيوتر Desktop برای ذخيره تنظيمات جاری اطلاعات محيط كار كاربر اختصاص داده می شود .
در يك برنامه وب ، داستان كاملا" متفاوت است . شايد از ديد كاربران يك سايت حرفه ای اينگونه برداشت شود كه يك برنامه بطور مستمر در حال اجراء است و به آنان سرويس های لازم را می دهد . علی رغم اين كه ظاهر موضوع درست بنظر می آيد ولی در پس پرده داستان بگونه ای ديگر دنبال می شود . برنامه های وب از يك الگوی دستيابی غيرمتصل كارآ استفاده می نمايند . در اين الگو ، سرويس گيرنده پس از ارتباط با سرويس دهنده از آن درخواست يك صفحه را می نمايد . پس از پاسخ به سرويس گيرنده ،ارتباط منطقی ايجاد شده قطع و سرويس دهنده بی خيال هر گونه اطلاعاتی در رابطه با سرويس گيرنده می گردد . پس از دريافت صفحه درخواستی توسط سرويس گيرنده ، برنامه اجراء خود را متوقف و ASP.NET engineاشياء مربوط به صفحه را دور می اندازد .
با توجه به اين كه سرويس گيرندگان لازم است در اكثر موارد صرفا" برای چندين ثانيه متصل باشند ، يك سرويس دهنده وب می تواند به هزاران درخواست با كارآئی مطلوب پاسخ دهد .
در صورتی كه لازم است اطلاعات بين چندين عمليات كاربر نگهداری شوند ، می بايست از راهكارهای مختلفی به منظور مديريت stateاستفاده كرد .
View state
همانگونه كه اطلاع داريد كنترل های سرويس دهنده ASP.NETاز viewstateبرای بخاطر سپردن stateاستفاده می نمايند . اطلاعات view stateدر يك فيلد مخفی نگهداری شده و بطور اتوماتيك پس از هر postbackبرای سرويس دهنده ارسال می گردد . view stateمحدود به كنترل های سرويس دهنده نمی گردد و در صورت ضرورت می توان مجموعه ای از اطلاعات مورد نياز را مستقيما" در مجموعه view stateذخيره تا امكان بازيابی آنها پس از هر postbackفراهم شود . نوع های مختلفی را می توان در view stateذخيره نمود . نوع های داده ساده و اشياء سفارشی نمونه هائی در اين زمينه می باشند .
خصلت ViewStateصفحه، مجموعه view stateرا ارائه می نمايد . اين خصلت يك نمونه از كلاس مجموعه StateBagاست .برای اضافه كردن و حذف آيتم هائی در اين كلاس ، از گرامری مشابه با يك ديكشنری استفاده می گردد كه در آن هر آيتم دارای يك نام منحصر بفرد است .
كد زير نحوه استفاده از view stateرا نشان می دهد .
ViewState("Counter") = 1 |
دستور فوق ، مقدار 1 را در مجموعه view stateقرار داده و به آن يك نام منحصربفرد را نسبت می دهد ( Counter ) . در صورتی كه آيتم ديگری با همين نام در view stateموجود نباشد ، يك آيتم جديد بطور اتوماتيك به آن اضافه می گردد . در صورتی كه يك آيتم با نام Counterدر view stateموجود باشد ، با مقدار فوق جايگزين می گردد .
برای بازيابی آيتم های ذخيره شده در view state از نام نسبت داده شده به هر يك از آنها استفاده می گردد . همچنين ، لازم است كه مقدار بازيابی شده را با استفاده از گرامر castingبه نوع داده مناسب تبديل نمود چراكه مجموعه ViewStateتمامی آينم ها را به عنوان اشياء عام ذخيره می نمايد تا بتواند با نوع های داده مختلف سرو كار داشته باشد .
كد زير نحوه بازيابی مقدار نسبت داده شده به Counter از viewstate و تبديل آن به يك عدد صحيح را نشان می دهد .
Dim counter As Integer counter = CType(ViewState("Counter"), Integer) |
در صورت عدم وجود اطلاعات مورد نظر در view stateبا يك NullReferenceExceptionمواجه خواهيم شد . بنابراين ، لازم است كه همواره قبل از بازيابی و تبديل داده ذخيره شده در view stateاز وجود آن در ساختار فوق اطمينان حاصل نمود .
برای آشنائی بيشتر با نحوه بكارگيری view stateدر برنامه های وب به بررسی يك نمونه مثال كاربردی خواهيم پرداخت .
مثال : ثبت تعداد دفعاتی كه بر روی يك دكمه كليك می گردد
كد زير يك برنامه ساده شمارنده را نشان می دهد كه در آن تعداد دفعاتی كه بر روی يك دكمه كليك می شود تشخيص داده شده و تعداد آن در خروجی نمايش داده می شود . بدون استفاده از يك راهكار مناسب برای مديريت state، شمارنده بطور دائم عدد 1 را در خروجی نشان خواهد داد .
برای ايجاد خروجی مورد نظر ، می بايست از يك راهكار مناسب (view state) جهت مديريت stateاستفاده گردد .
<%@ Page Language="VB" Culture="fa-IR" UICulture="fa-IR" %> <script runat="server"> Sub cmdIncrement_Click(ByVal sender As Object,ByVal e As EventArgs) Handles cmdIncrement.Click Dim Counter As Integer If ViewState("Counter") Is Nothing Then Counter = 1 Else Counter = CType(ViewState("Counter"), Integer) + 1 End If ViewState("Counter") = Counter lblCount.Text = "مقدار شمارنده برابر است با: " & Counter.ToString() End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" dir="rtl" > <head id="Head1" runat="server"> <title>تستview state </title> </head> <body style="font-family: Tahoma"> <form id="form1" runat="server"> <div> <asp:Button ID="cmdIncrement" runat="server" Text="افزايش شمارنده" Font-Names="Tahoma" /><br /><br /> <asp:Label ID="lblCount" runat="server" Font-Names="Tahoma"></asp:Label> </div> </form> </body> </html> |
در كد فوق قبل از تلاش برای بازيابی آيتم مورد نظر از view state، وجود آن در ساختار فوق بررسی می گردد .
برای حل مسئله مديريت stateدر مثال فوق و نگهداری مقدار counterدر بين چندين postbackاز روش هائی ديگر نيز می توان استفاده كرد . به عنوان مثال
، می توان برای كنترل سرويس دهنده labelويژگی view stateرا فعال و از labelبرای ذخيره مقدار counterاستفاده نمود . هر مرتبه كه بر روی دكمه "افزايش شمارنده " كليك گردد ، مقدار جاری از طريق خصلت textكنترل labelبازيابی و پس از تبديل به يك عدد صحيح در خروجی نمايش داده می شود .
از روش فوق نمی توان همواره به عنوان يك راهكار مناسب استفاده كرد . مثلا" ممكن است قصد ايجاد برنامه ای را داشته باشيم كه تعداد دفعاتی را كه بر روی يك دكمه كليك می گردد ثبت نمايد ولی قصد نمايش نتايج را در خروجی نداشته باشيم . در چنين مواردی می توان همچنان اطلاعات را در يك كنترل سرويس دهنده ذخيره نمود ولی مجبور خواهيم بود كه آن را مخفی نگاه داريم .
پس از انجام تمامی اين كارها ، به چيزی می رسيم كه view state آن را در اختيار ما قرار می دهد . view state، اطلاعات را بطور اتوماتيك در يك فيلد مخفی خاص در صفحه نگهداری می نمايد . با توجه به اين كه ASP. NETبا جزئيات اين كار سروكار دارد ، كد نوشته شده توسط پياده كنندگان از خوانائی بيشتری برخوردار خواهد بود .
در بخش دوم به بررسی نحوه ايمن سازی اطلاعات ذخيره شده در view stateخواهيم پرداخت .
اطلاعات view stateدر يك رشته درهم آميخته مشابه زير ذخيره می گردد .
<input type="hidden" name="__VIEWSTATE" value="/wEPDwUKMTUyMzMyNzc3NGRklXVE/6qqfC5AWkr1Yw0Xu5IpHg0=" /> |
به موازات اضافه كردن اطلاعات بيشتر به view state، طول اين رشته طولانی تر خواهد شد . با توجه به اين كه مقدار ذخيره شده در رشته فوق به صورت متن شفاف نمی باشد ، بسياری از برنامه نويسان ASP.NETبر اين باور هستند كه داده ذخيره شده در view stateبه صورت رمز شده است .ولی واقعيت اينچنين نيست . در واقع ، اطلاعات view stateبه سادگی در حافظه به يكديگر متصل شده و به يك رشته Base64 تبديل می شوند .يك هكر باهوش می تواند با استفاده از مهندسی معكوس رشته فوق ، view stateرا بررسی و از اطلاعات ذخيره شده در آن به سرعت آگاه گردد .
كد زير نحوه رمز كردن يك رشته معمولی را به يك رشته Base64 نشان می دهد .
Private Function EncodeBase64(ByVal input As String) As String Dim strBytes() As Byte = System.Text.Encoding.UTF8.GetBytes(input) Return System.Convert.ToBase64String(strBytes) End Function |
كد زير نحوه رمز گشائی يك رشته Base64 را به يك رشته معمولی نشان می دهد .
Private Function DecodeBase64(ByVal input As String) As String Dim strBytes() As Byte = System.Convert.FromBase64String(input) Return System.Text.Encoding.UTF8.GetString(strBytes) End Function |
در صورتی كه لازم است اطلاعات ذخيره شده در view stateدارای ايمنی بيشتری باشند از دو گزينه مختلف می توان استفاده كرد :
گزينه اول : به ASP. NETاعلام شود كه از يك hash codeاستفاده نمايد. برخی اوقات از hashcodeبه عنوان يك checksumقدرتمند پنهانی نيز ياد می شود . در چنين مواردی ، ASP. NETتمامی داده ذخيره شده در view stateرا بررسی و يك الگوريتم hashingرا بر روی آن اعمال می نمايد . الگوريتم فوق يك سگمنت كوتاه از داده را ايجاد می نمايد كه در واقع همان hashcodeاست . در ادامه ، كد فوق به انتهای داده ذخيره شده در view stateاضافه می گردد .
زمانی كه صفحه post backمی گردد ، ASP. NETداده موجود در view stateرا بررسی و مجددا" hash codeرا با استفاده از فرآيندی مشابه توليد می نمايد . در ادامه مقدار محاسبه شده با مقدار موجود در رشته مقايسه می گردد تا اين اطمينان حاصل شود كه داده ذخيره شده در view stateتغيير نكرده باشد .
در صورتی كه يك كاربر بدانديش بخشی از داده موجود در viewstateرا تغيير دهد ، ASP. NETيك hash codeرا توليد خواهد كرد كه با كد ذخيره شده در انتهای view stateمطابقت نخواهد كرد . در صورت تحقق چنين شرايطی ، postback بطور كامل ناديده گرفته خواهد شد .
شايد در ذهن شما اين موضوع مطرح شده باشد كه يك كاربر باهوش می تواند با بكارگيری ترفندهائی بر مشكل اشاره شده غلبه كرده و علاوه بر توليد اطلاعات نادرست ، يك hash codeمناسب و منطبق بر اطلاعات ذخيره شده در view stateرا نيز توليد نمايد . در پاسخ می بايست به اين نكته اشاره كرد كه كاربران بدانديش قادر به توليد hash codeصحيح نخواهند بود چراكه آنان دارای كليد رمزنگاری مشابه ASP. NETنمی باشند . اين بدان معنی است كه hash codeتوليد شده با وضعيت موجود نمی تواند مطابقت نمايد .
hashcode بطور پيش فرض فعال است . بنابراين در صورت تمايل به استفاده از پتانسيل فوق ، لازم نيست كه مراحل اضافه ای را دنبال نمود . در برخی موارد پياده كنندگان ويژگی فوق را غيرفعال می نمايند تا از مشكلات احتمالی موجود در يك web farmپيشگيری نمايند . در چنين وضعيتی ، سرويس دهندگان مختلف دارای كليدهای مختلفی می باشند و مشكل زمانی اتفاق می افتد كه پس از post backصفحه ، يك سرويس دهنده جديد آن را دريافت نمايد .
در يك محيط web farmكليد می بايست در بين تمامی سرويس دهندگان يكسان باشد . در صورتی كه كليد يكسان نباشد و صفحه برای يك سرويس دهنده متفاوت با سرويس دهنده ای كه صفحه را ايجاد كرده است ، postbackگردد ، يك خطاء ايجاد خواهد شد .بنابراين در يك محيط web farm، می بايست پياده كنندگان يك كليد را در فايل Machine.configمشخص نمايند ( در مقابل اين كه به ASP.NETاجازه داده شود كه اين كليد را بطور اتوماتيك ايجاد نمايد) .
برای غيرفعال كردن hash codes، می بايست از خصلت enableViewStateMacعنصر <pages> در فايل web.config و يا machine.config به صورت زير استفاده كرد .
<configuration > <system.web> <pages enableViewStateMac="false" /> ... </system.web> </configuration> |
گزينه دوم : با ايجاد يك كد hash، فريمورك ASP. NETاين موضوع را بررسی خواهد كرد كه آيا داده ذخيره شده در view stateدستكاری شده است ؟ علی رغم ايجاد اين لايه امنيتی ، داده موجود در view stateهمچنان قابل مشاهده توسط كاربران بدانديش خواهد بود .
در صورتی كه بر روی داده ذخيره شده در view stateحساسيت زيادی وجود داشته باشد و بخواهيم امنيت آن را افزايش دهيم ، می بايست رمزنگاری view stateرا فعال كرد . برای فعال كردن ويژگی فوق از خصلت ViewStateEncryptionMode به همراه دايركتيو pageاستفاده می گردد .
<%@Page ViewStateEncryptionMode="Always" %> |
در صورت تمايل می توان از خصلت فوق در فايل پيكربندی نيز استفاده كرد .
<configuration > <system.web> <pages viewStateEncryptionMode="Always" /> ... </system.web> </configuration> |
به خصلت ViewStateEncryptionModeيكی از مقادير زير را می توان نسبت داد :
- Always: همواره رمزنگاری انجام می شود .
- Never: رمزنگاری انجام نخواهد شد .
- Auto: رمزنگاری صرفا" در مواردی كه يك كنترل با صراحت آن را درخواست نمايد ، انجام خواهد شد .
گزينه پيش فرض Autoاست . اين بدان معنی است كه يك كنترل با فراخوانی متد Page.RegisterRequiresViewStateEncryptionرمزنگاری را درخواست می نمايد . در صورتی كه يك كنترل به دليل عدم داشتن اطلاعات حساس از متد فوق استفاده نكند ، view stateرمز نخواهد شد و عمليات بيشتری جهت رمزنگاری به سيستم تحميل نخواهد شد . به عبارت ديگر ، يك كنترل زمانی می تواند دل خود را به خدمات ارائه شده توسط متد فوق خوش نمايد كه مقدار خصلت viewStateEncryptionMode، معادل Autoدر نظر گرفته شده باشد . در صورتی كه مقدار خصلت فوق Neverدر نظر گرفته شده باشد ، به درخواست كنترل ها جهت رمزنگاری پاسخ داده نخواهد شد.
با توجه به اين كه رمزنگاری عمليات بيشتری را به سرويس دهنده وب تحميل می نمايد ( هم در زمان رمزنگاری و هم در زمان رمزگشائی پس از هر post back) در صورت عدم نياز به پتانسيل فوق و به منظور عدم تاثيرگذاری آن بر روی كارآئی برنامه های وب ، ضرورتی به فعال كردن آن وجود ندارد .
انواع متغيرها
پس از ايجاد ساختار اوليه يك كلاس ، می بايست عناصر داده پايه را به آن اضافه نمود .
در كد زير ، سه membervariableتعريف شده است كه اطلاعاتی را در ارتباط با product( شامل نام ، قيمت و URLآن كه به يك فايل imageاشاره می نمايد ) در خود نگهداری می نمايند .
Public Class Product Private name As String Private price As Decimal Private imageUrl As String End Class |
يك متغير محلی صرفا" تا زمانی وجود خواهد داشت كه فعاليت متد جاری ادامه داشته باشد . به عبارت ديگر ، يك membervariable( و يا فيلد ) به عنوان بخشی از كلاس تعريف می شود و برای تمامی متدهای موجود در كلاس قابل دسترس و استفاده است . اين نوع متغيرها ، پس از ايجاد شی ، ايجاد خواهند شد و تا زمانی كه شی مورد نظر به فعاليت خود ادامه می دهد ، آنها نيز فعال و به حيات خود ادامه خواهند داد .
زمانی كه يك membervariableتعريف می گردد ، می بايست با صراحت قابليت دستيابی به آن مشخص گردد . قابليت دستيابی پذيری مشخص می نمايد كه چه بخش هائی از كد قادر به خواندن و تغيير اين متغير می باشند . مثلا" اگر شی Aشامل يك متغير خصوصی (Private) باشد ، شی Bقادر به خواندن و تغيير آن نخواهد بود و صرفا" شی Aقادر به انجام اين كار خواهد بود . به عبارت ديگر ، اگر شی Aدارای يك متغير عمومی ( public) باشد ، هر شی ديگر موجود در برنامه اين آزادی عمل را خواهد داشت كه اقدام به خواندن و تغيير اطلاعات ذخيره شده در آن نمايد . متغيرهای محلی از هيچگونه كليد واژه قابليت دستيابی پذيری حمايت نمی نمايند چراكه اين نوع متغيرها هرگز نمی توانند برای ساير كد های موجود در خارج از متد جاری در دسترس باشند .
در يك برنامه ساده ASP. NET، اكثر membervariablesخصوصی خواهند بود چراكه اكثر كد نوشته شده توسط پياده كنندگان در يك كلاس صفحه وب قرار می گيرد .
به موازات تلاش جهت ايجاد عناصر نرم افزاری با قابليت استفاده مجدد ، اهميت قابليت دستيابی پذيری افزايش خواهد يافت .
جدول 1 انواع سطوح دستيابی را نشان می دهد .
كليدواژه | قابليت دستيابی پذيری |
Public | امكان دستيابی توسط هر كلاس |
Private | صرفا" امكان دستيابی به آن توسط متدهای درون كلاس جاری وجود دارد . |
Friend | امكان دستيابی به آن توسط متدهای موجود در هر كلاس موجود در اسمبلی جاری ( فايل كد ترجمه شده ) وجود دارد. |
Protected | امكان دستيابی به آن توسط متدهای موجود در كلاس جاری و يا هر كلاسی كه از اين كلاس مشتق شده باشد ، وجود دارد |
جدول 1 انواع سطوح دستيابی
توجه داشته باشيد كه كليد واژه " قابليت دستيابی پذيری " ، صرفا" در ارتباط با membervariableبكار گرفته نمی شود و از آن در ارتباط با متدها ، خصلت ها و رويدادها نيز استفاده می گردد.
ذخيره Member variablesدر view state
هر گونه اطلاعاتی كه در يك member variableصفحه ASP. NETذخيره می گردد ، بطور اتوماتيك و پس از اتمام پردازش و ارسال صفحه برای سرويس گيرنده از بين می رود ( به عنوان نمونه متغير counterدر مثال ارائه شده در ) .
برای حل مشكلاتی اين چنين می توان تمامی membervariablesرا در زمان بروز رويداد Page.PreRenderدر view stateذخيره و زمانی كه رويداد Page.Loadايجاد می گردد آنها را بازيابی كرد . رويداد Loadهر مرتبه كه صفحه ايجاد می شود ، محقق می گردد . در زمان postback ، در ابتدا رويداد Loadمحقق شده و در ادامه ساير رويدادهای مرتبط با كنترل ها ايجاد خواهند شد .
مثال
در كد زير از روش فوق در ارتباط با متغيری با نام Contents، استفاده شده است . در اين صفحه يك text boxبه همراه دو buttonارائه شده است . كاربر در خصوص ذخيره و يا بازيابی داده از viewstateتصميم می گيرد . روتين های مربوط به رويداد كليك هر يك از دكمه های موجود بر روی فرم ، مسئوليت ذخيره و بازيابی داده در متغير Contentsرا برعهده دارند . در روتين های فوق ضرورتی به ذخيره و بازيابی مقدار متغير contentsدر viewstateوجود ندارد چراكه اين مسئوليت به روتين های رويدادهای Loadو PreRenderواگذار شده است تا در زمان آغاز و اتمام پردازش صفحه ، وظايف اشاره شده ( ذخيره و بازيابی ) را انجام دهند .
<%@ Page Language="VB" Culture="fa-IR" UICulture="fa" %> <script runat="server"> Private Contents As String Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load If Me.IsPostBack Then Contents = CType(ViewState("Text"), String) End If End Sub Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender ViewState("Text") = Contents End Sub Protected Sub cmdSave_Click(ByVal sender As Object, ByVal e As EventArgs)Handles cmdSave.Click Contents = txtValue.Text txtValue.Text = "" End Sub Protected Sub cmdLoad_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdLoad.Click txtValue.Text = Contents End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" dir="rtl" > <head id="Head1" runat="server"> <title>ذخيرهmember variable</title> </head> <body style="font-family: Tahoma"> <form id="form1" runat="server"> <div> <asp:TextBox ID="txtValue" runat="server" Height="153px" TextMode="MultiLine" Width="341px" Font-Names="Tahoma" >نحوه ذخيره و بازيابیمتغيرهایmember درview state</asp:TextBox><br /> <br /> <asp:Button ID="cmdSave" runat="server" Text="ذخيره" Font-Names="Tahoma" /> <asp:Button ID="cmdLoad" runat="server" Text="بازيابی" Font-Names="Tahoma" /> </div> </form> </body> </html> |
شكل 1 ، خروجی برنامه فوف را نشان می دهد .
شكل 1 : ذخيره Member variablesدر view state
در زمان استفاده از روش فوق می بايست از ذخيره اطلاعات غيرضروری در view stateاجتناب گردد ، چراكه در چنين مواردی ، حجم خروجی صفحه نهائی افزايش می يابد . اين موضوع می تواند كاهش سرعت انتقال صفحه را به دنبال داشته باشد .
در صورت ضرورت استفاده از رويكرد فوق برای ذخيره member variableدر view state، می بايست از آن در موارد كاملا" خاص استفاده شود .
ذخيره اشياء سفارشی
پياده كنندگان می توانند اشياء خود را در view stateذخيره نمايند . برای ذخيره يك آيتم در view state، می بايست امكان تبديل آن به مجموعه ای از بايت ها وجود داشته باشد تا بتوان آن را در يك فيلد ورودی مخفی ذخيره كرد . به فرآيند فوق serialization می گويند . در صورتی كه اشياء تعريف شده توسط پياده كنندگان قابليت تبديل به مجموعه ای از بايت ها را نداشته باشند ( به صورت پيش فرض اين قابليت وجود ندارد ) ، در زمان استقرار آنها در view stateيك پيام خطاء مواجه خواهيم شد.
برای ايجاد قابليت serializable در اشياء تعريف شده ، می بايست خصلت [Serializable] را به تعريف كلاس اضافه كرد .
كد زير نحوه انجام اين كار را نشان می دهد .
<Serializable()> _ Public Class Customer Public FirstName As String Public LastName As String Public Sub New(ByVal firstName As String, ByVal lastName As String) Me.FirstName = firstName Me.LastName = lastName End Sub End Class |
با توجه به اين كه در كلاس Customerخصلت serializableتعريف شده است ، امكان ذخيره آن در view stateوجود خواهد داشت .
Dim customer1 As New Customer("MyFirstName","MyLastName") ViewState("CurrentCustomer") = customer1 |
در زمان استفاده از اشياء سفارشی ، پس از بازيابی داده از view state می بايست آنها را تبديل ( cast) كرد.
Dim customer1 As Customer customer1 = CType(ViewState("CurrentCustomer"), Customer) |
آيا تمامی اشياء دات نت را می توان در view stateذخيره كرد ؟
پس از بررسی مستندات يك كلاس و با مشاهده خصلت [Serializable] ، امكان ذخيره شی مورد نظر در view stateوجود خواهد داشت . در صورتی كه شی مورد نظر فاقد خصلت فوق باشد ، امكان ذخيره آن در view stateوجود نخواهد داشت . در چنين مواردی می توان از روش های ديگر state management كه در بخش های بعدی به آنها اشاره خواهيم كرد، استفاده كرد.
يكی از مهمترين محدوديت های view state، شعاع استفاده از اطلاعات ذخيره شده در آن توسط ساير صفحات وب است . اطلاعات ذخيره شده در view stateصرفا" توسط صفحه ای كه آنها را ايجاد كرده است قابل استفاده خواهند بود و ساير صفحات قادر به استفاده از اطلاعات نخواهند بود . به عنوان مثال ، در صورتی كه كاربر به صفحه ای ديگر حركت و يا هدايت شود ، اطلاعات ذخيره شده در viewstateقابل دستيابی نبوده و عملا" از بين خواهند رفت . برای غلبه بر محدوديت فوق ، از روش های متعدد ديگری می توان استفاده كرد .
يكی از مهمترين محدوديت های view state، شعاع استفاده از اطلاعات ذخيره شده در آن توسط ساير صفحات وب است . اطلاعات ذخيره شده در view stateصرفا" توسط صفحه ای كه آنها را ايجاد كرده است قابل استفاده می باشند و ساير صفحات نمی توانند از اطلاعات فوق استفاده نمايند . به عنوان مثال ، در صورتی كه كاربر به صفحه ای ديگر حركت و يا هدايت شود ، اطلاعات ذخيره شده در viewstateقابل دستيابی نبوده و عملا" از بين خواهند رفت . برای غلبه بر محدوديت فوق ( انتقال اطلاعات از يك صفحه به صفحه ديگر )، از روش های متعدد ديگری می توان استفاده كرد .
در اين بخش ، با روش انتقال اطلاعات از يك صفحه به صفحه ای ديگر با استفاده از cross-page posting آشنا خواهيم شد .
cross-page posting
يكی از امكانات جديد ارائه شده در ASP. NET 2.0 ، امكان postbackيك صفحه به صفحه ای ديگر است ( برخلاف گزينه پيش فرض كه هر صفحه به خود postbackمی گردد ) .
بدين منظور خصلت جديد PostBackUrlدر كنترل هائی نظير ImageButton ، LinkButtonو Buttonپيش بينی شده است . برای استفاده از ويژگی فوق كافی است مقدار PostBackUrlبرابر با آدرس صفحه مقصد در نظر گرفته شود . بدين ترتيب ، پس از كليك بر روی دكمه موجود بر روی فرم ، صفحه به همراه تمامی مقادير كنترل های ورودی موجود بر روی آن برای آدرس مشخص شده ارسال می گردد .
مثال
در اين مثال بر روی فرم وب از دو كنترل textboxو يك كنترل buttonاستفاده شده است ( صفحه Crosspage1.aspx) . پس از كليك بر روی دكمه موجود بر روی فرم ، اطلاعات موجود بر روی فرم برای صفحه ای با نام Crosspage2.aspxارسال می گردد .
صفحه Crosspage1.aspx |
<%@ Page Language="VB" Culture="fa-IR" UICulture="fa-IR" %> |
صفحه Crosspage1.aspxشامل هيچگونه كدی نمی باشد .
پس از اجرای كد فوق و كليك بر روی دكمه "ارسال به صفحه ديگر" ، صفحه برای CrossPage2.aspxارسال می گردد . صفحه Crosspage2.aspxبا استفاده از خصلت Page.PreviousPageقادر به برقراری ارتباط با صفحه Crosspage1.aspxخواهد بود .
كد زير نحوه دريافت و نمايش عنوان صفحه قبلی را در صفحه Crosspage2.aspx نشان می دهد .
صفحه Crosspage2.aspx |
<%@ Page Language="VB" Culture="fa-IR" UICulture="fa-IR" %> |
در صفحه Crosspage2.aspxقبل از تلاش برای دستيابی به شی PreviousPage، مقدار آن با nullبررسی می گردد . در صورتی كه مقدار آن معادل nullباشد ، cross-page postback محقق نشده است . اين بدان معنی است كه صفحه Crosspage2.aspxمستقيما" درخواست شده است و يا به خود postbackشده است . در چنين مواردی شی PreviousPageقابل دسترس نخواهد بود .
نحوه دريافت اطلاعات از صفحه مبداء
شايد برای بسياری از خوانندگان اين سوال مطرح شده باشد كه چگونه می توان اطلاعاتی ديگر نظير مقادير درج شده در textboxesموجود بر روی صفحه مبداء ( CrossPage1.aspx) را در صفحه مقصد ( CrossPage2.aspx) بازيابی و از آنها استفاده كرد؟
در زمان پيكربندی يك صفحه برای عمليات cross-page posting، اغلب پياده كنندگان تمايل به دريافت اطلاعات از صفحه مبداء را دارند . اين اطلاعات می تواند شامل كنترل های موجود بر روی صفحه مبداء و يا خصلت های عمومی صفحه مبداء باشد .
دريافت مقادير كنترل ها
كلاس Pageدارای يك خصلت با نام PreviousPageاست . در صورتی كه صفحه مبداء و مقصد در يك برنامه مشابه ASP.NETباشند ، خصلت PreviousPageدر صفحه مقصد شامل يك مرجع به صفحه مبداء خواهد بود .
در صورتی كه صفحات مبداء و مقصد در برنامه های متفاوتی باشند ، خصلت PreviousPageمقداردهی اوليه نخواهد شد و نمی توان از طريق صفحه مقصد مستقيما" به مقادير كنترل های موجود بر روی صفحه مبداء دستيابی داشت . در چنين مواردی می توان با استفاده از ديكشنری Formاقدام به خواندن داده ارسال شده نمود . همچنين ، در چنين مواردی امكان خواندن مقادير ذخيره شده در view stateصفحه مبداء وجود نخواهد داشت . در صورتی كه بخواهيم مقاديری را در صفحه مبداء ذخيره نمائيم و آنها را برای يك صفحه مقصد و در يك برنامه ديگر قابل دسترس نمائيم ، می توان مقادير مورد نظر را به عنوان رشته درون فيلدهای مخفی بر روی صفحه مبداء ذخيره و در صفحه مقصد از طريق Request.Formبه آنها دستيابی داشت .
با استفاده از مرجع در خصلت PreviousPageمی توان عمليات جستجو بر روی صفحه مبداء را به منظور بازيابی و استخراج مقادير مورد نظر توسط متد FindControlانجام داد .
كد زير نحوه بازيابی مقادير درج شده در كنترل های Textboxموجود بر روی صفحه مبداء (CrossPage1.aspx) را نشان می دهد .
بازيابی مقادير كنترل های موجود در صفحه مبداء (Script sectionصفحه مقصد ) |
<script runat="server"> |
متد FindControl، كنترل های مورد نظر را در فهرست جاری نام جستجو می نمايد . در صورتی كه قصد جستجوی كنترل موجود در كنترل ديگر را داشته باشيم ( نظير يك تمپليت ) ، می بايست در ابتدا يك مرجع به آْن را ايجاد و عمليات جستجو را در آن فهرست انجام داد .
دريافت مقادير خصلت های عمومی
در صفحه مقصد يك cross-page posting، می توان مقادير فيلدهای عمومی موجود در صفحه مبداء را دريافت كرد . بدين منظور صفحه مبداء خصلت هائی عمومی را با توجه به اهداف عملياتی برنامه تعريف می نمايد تا در ادامه صفحه مقصد بتواند به آنها دستيابی داشته باشد ( به عنوان يك توصيه امنيتی پيشنهاد شده است كه حجم اين گونه اطلاعات عمومی كمتر در نظر گرفته شود تا آسيب پذيری كد در مقابل حملات كاهش يابد ) .
برای دريافت فيلدهای عمومی صفحه مبداء ، در ابتدا می بايست يك مرجع به صفحه مبداء را تعريف كرد . بدين منظور می توان از روش های مختلفی استفاده كرد . استفاده از دايركتيو PreviousPageType@ در صفحه مقصد كه به كمك آن صفحه مبداء مشخص می گردد ، يكی از روش های موجود در اين زمينه است .
كد زير نحوه انجام اين كار را مشخص می نمايد .
<%@ PreviousPageType VirtualPath="~/CrossPage1.aspx" %> |
پس از استقرار دايركتيو فوق در صفحه مقصد ، خصلت PreviousPageبه كلاس صفحه مبداء اشاره خواهد داشت ( به عنوان يك مرجع ) . در ادامه می توان مستقيما" به اعضاء عمومی موجود در صفحه مبداء دستيابی داشت .
به عنوان مثال، در صورتی كه قصد داشته باشيم از مقادير موجود در دو text boxصفحه مبداء استفاده نمائيم (صفحه CrossPage1.aspx)، می توان خصلت هائی را به منظور استفاده از متغيرهای كنترل به آن اضافه كرد .
در نمونه كد زير دو خصلت به كلاس CrossPage1 اضافه شده است تا بتوان از مقادير كنترل های Textboxدر صفحه مقصد استفاده كرد .
Public ReadOnly Property FirstNameTextBox() As TextBox Get Return txtFirstName End Get End Property Public ReadOnly Property LastNameTextBox() As TextBox Get Return txtLastName End Get End Property |
در صورتی كه صفحه مقصد شامل دايركتيو PreviousPageTypeباشد كه توسط آن به صفحه مبداء اشاره می گردد ، می توان به خصلت های عمومی صفحه مبداء دستيابی داشت .
مثال
برای بازنويسی مثال ارائه شده در بخش چهارم مراحل زير را انجام می دهيم .
مرحله اول: بازنويسی صفحه CrossPage1.aspx. كد فوق مشابه كد نوشته شده در بخش چهارم است با اين تفاوت كه برای دستيابی به نام و نام حانوادگی درج شده در هر Textboxيك خصلت عمومی با نام FullNameتعريف شده است تا نام و نام خانوادگی ورودی را در خود نگهداری نمايد .
صفحه Crosspage1.aspx |
<%@ Page Language="VB" Culture="fa-IR" UICulture="fa-IR" %> |
بدين ترتيب ، ارتباط بين دو صفحه واضح و ساده بوده و نگهداری آنها نيز آسان خواهد شد . همچنين ، می توان كنترل ها را در صفحه مبداء ( CrossPage1 ) بدون نياز به تغيير ساير بخش های برنامه تغيير داد . مثلا" ، در صورتی كه تصميم داشته باشيم از كنترل های مختلفی برای درج نام در صفحه CrossPage1.aspxاستفاده نمائيم ، می بايست بر روی كد مربوط به خصلت FullNameمتمركز گرديد . تغيير فوق صرفا" در صفحه CrossPage1.aspxاعمال می گردد و ضرورتی به تغيير صفحه CrossPage2.aspxوجود نخواهد داشت .
مرحله دوم : بازنويسی صفحه CrossPage2.aspx. در بازنويسی صفحه فوق در اولين اقدام از دايركتيو PreviousPageType@ برای معرفی كلاس صفحه مبداء استفاده شده است تا به كمك آن بتوان به اعضاء عمومی تعريف شده در صفحه مبداء دستيابی داشت . برای دستيابی به خصلت عمومی FullName، از PreviousPage.FullNameاستفاده شده است .
صفحه Crosspage2.aspx |
<%@ Page Language="VB" Culture="fa-IR" UICulture="fa-IR" %> |
شكل 1 ، خروجی برنامه فوق را نشان می دهد .
شكل 1 : دستيابی به مقادير كنترل ها در صفحه مقصد
ASP. NETاز ويژگی جالبی برای كاركرد بهتر فرآيند cross-page postbacksاستفاده می نمايد . اولين مرتبه ای كه صفحه مقصد به Page.PreviousPageدستيابی پيدا می نمايد ، ASP. NET شی صفحه قبلی ( مبداء) را ايجاد نمايد . برای انجام اين كار ، ASP. NETپردازش صفحه را آغاز ولی آن را قبل از رسيدن به مرحله PreRenderبا وقفه متوقف نموده و اجازه نمی دهد كه صفحه خروجی HTMLرا توليد نمايد .
روش فوق دارای اثرات جانبی جالبی است ، مثلا" تمامی رويدادهای صفحه مربوط به صفحه قبل ( نظير Page.Load و Page.Init) به همراه رويداد Button.Click كه باعث cross-page postbackشده است ، فعال می گردند . علت فعال شدن رويدادهای فوق ، مقدار دهی صفحه مبداء توسط ASP.NETاست .
بررسی PostBackدر صفحه مقصد
در حين فرآيند cross-page postback، محتويات كنترل های موجود بر روی صفحه مبداء برای صفحه مقصد ارسال و مرورگر يك عمليات HTTP POSTرا انجام می دهد ( نه عمليات GET) . در صفحه مقصد و بلافاصله پس از عمليات postback، مقدار خصلت IsPostBackبرابر با falseخواهد شد . با اين كه ماهيت عمليات در واقع يك POSTرا نشان می دهد ، ولی cross-posting بيانگر يك Postbackبه صفحه مقصد نمی باشد .
در برخی موارد لازم است كه در يك برنامه تشخيص دهيم كه آيا يك cross-page postواقع شده است. در چنين مواردی می توان مقدار خصلت IsCrossPagePostBack را بر روی صفحه مقصد كه توسط خصلت PreviousPage صفحه مقصد برگردانده شده است ، بررسی كرد .
كد زير نحوه انجام اين كار را نشان می دهد .
If PreviousPage IsNot Nothing Then If PreviousPage.IsCrossPagePostBack = True Then Label1.Text = "Cross-page post." End If Else Label1.Text = "Not a cross-page post." End If |
QueryString
يكی ديگر از روش های ارسال اطلاعات بين صفحات ، استفاده از يك query stringدر URLاست . از رويكرد فوق در موتورهای جستجو استفاده می گردد . مثلا" در صورت استفاده از موتور جستجوی گوگل ، پس از درج كليد واژه مورد نظر به يك URLجديد كه با پارامترهای جستجو تركيب می گردد ، هدايت می شويم.
http://www.google.com/search?q=web application+ASP.NET |
querystring، به عنوان بخشی از URLمحسوب می گردد و پس از علامت سوال آورده می شود . در مثال فوق ، يك متغير با نام qو مقدار web application +ASP. NETتعريف می گردد .
يكی از مهمترين مزايای query string، عدم تحميل عمليات اضافه به سرويس دهنده است . علی رغم مزيت فوق ، رويكرد فوق دارای محدوديت های متعددی است كه به برخی از آنها اشاره می گردد :
- اطلاعات محدود به رشته های ساده مشتمل بر كاراكترهای مجاز URLاست .
- اطلاعات توسط كاربران قابل مشاهده بوده و هر شخص علاقه مند می تواند آنها را استراق سمع نمايد .
- كاربران ماهر می توانند محتويات query stringرا تغيير داده و وضعيتی را ايجاد نمايند كه برنامه قادر به واكنش مناسب با آن نباشد ( ايجاد شرايط غيرقابل پيش بينی )
- تعداد زيادی از مرورگرها دارای محدوديت طول يك URLمی باشند ( معمولا" بين يك تا دو كيلو بايت ) . بنابراين ، نمی توان حجم بالائی از اطلاعات را در querystringذخيره كرد و اين اطمينان را داشت كه اكثر مرورگرها بتوانند از آن استفاده نمايند ( به دليل عدم سازگاری ) .
علی رغم محدوديت های اشاره شده ، query stringهمچنان به عنوان مكانيزمی جهت ارسال داده از يك صفحه به صفحه ديگر استفاده می گردد . روش فوق در برنامه هائی با محوريت بانك های اطلاعاتی بيشتر مورد توجه است . در چنين مواردی در آغاز ليستی از آيتم هائی نمايش داده می شود كه متاثر از داده موجود در بانك اطلاعاتی است ( نظير ليست محصولات ، ليست مقالات ، ليست خبرها و ... ) . پس از انتخاب يكی از آيتم ها ، كاربر به صفحه ای ديگر هدايت می گردد تا اطلاعات بيشتری در ارتباط با آيتم انتخابی در اختيار وی گذاشته شود .
برای ذخيره اطلاعات در querystring، می بايست پياده كنندگان خود اطلاعات را در مكان مورد نظر قرار دهند . متاسفانه ، روشی مبتنی بر collectionبرای انجام اين كار وجود ندارد . اين بدان معنی است كه در چنين مواردی می بايست عموما" از يك كنترل Hyperlinkخاص و يا عبارت Response.Redirectاستفاده كرد .
كد زير كاربر را به صفحه newspage.aspxهدايت می نمايد . همراه با كاربر متغيری با نام recordIDو مقدار 10 نيز برای صفحه فوق ارسال می گردد .
Response.Redirect("newpage.aspx?recordID=10") |
در صورت نياز می توان چندين پارامتر را كه توسط علامت & ( ampersand) از يكديگر جدا می شوند ، به صفحه مقصد ارسال كرد .
كد زير كاربر را به صفحه newspage.aspxهدايت می نمايد . همراه با كاربر متغيرهائی با نام recordIDو مقدار 10 و mode با مقدار fullنيز برای صفحه فوق ارسال می گردد .
Response.Redirect("newpage.aspx?recordID=10&mode=full") |
صفحه دريافت كننده به سادگی می تواند اطلاعات ارسالی را دريافت نمايد . بدين منظور از مجموعه ديكشنری QueryStringكه توسط شی Requestارائه شده است ، استفاده می گردد .
Dim ID As String = Request.QueryString("recordID") |
توجه داشته باشيد كه اطلاعات همواره به عنوان يك رشته بازيابی می گردند و در صورت نياز می بايست آنها را به نوع داده مورد نظر تبديل كرد . مقادير ذخيره شده در مجموعه QueryString ، با استفاده از اسامی متغيرها ، ايندكس می گردند .
query stringبر خلاف view state، اطلاعات را كاملا" شفاف ، آشكارا و غير رمز شده ارسال می نمايد . بنابراين در مواردی كه لازم است اطلاعاتی بطور مخفی از يك صفحه به صفحه ديگر ارسال و يا بر روی آن حساسيت خاصی از نظر امنيتی وجود دارد ، استفاده از روش query stringتوصيه نمی گردد .
مثال
در اين مثال هدف بررسی نحوه عملكرد و يا رفتار query stringبا استفاده از دو صفحه است . در صفحه مبداء ليستی از آيتم ها در اختيار كاربر گذاشته می شود . پس از انتخاب يكی از آيتم ها ، كاربر به يك صفحه جديد ( مقصد ) هدايت می گردد . در صفحه مقصد آيتم انتخاب شده به همراه مقدار متغير mode در خروجی نمايش داده می شود .
صفحه مبداء شامل ليستی از آيتم ها ، يك check box، يك كنترل labelو دكمه "مشاهده اطلاعات " است .
كد زير محتويات صفحه مبداء ( QueryStringSender.aspx) را نشان می دهد .
صفحه QueryStringSender.aspx |
<%@ Page Language="VB" Culture="fa-IR" UICulture="fa-IR" %> |
كد زير محتويات صفحه مقصد ( QueryStringRecipient.aspx ) را نشان می دهد .
صفحه QueryStringRecipient.aspx |
<%@ Page Language="VB" Culture="fa-IR" UICulture="fa-IR" %> |
URLEncoding
يكی از مسائل در ارتباط با روش querystring، استفاده از كاراكترهای غيرمجاز در يك URLاست . ليست كاراكترهای مجاز در يك URLبمراتب كمتر از ليست كاراكترهای مجاز در يك سند HTMLاست . تمامی كاراكترها می بايست الفبا عددی و يا يكی از مجموعه كاراكترهای خاص نظير $-_.+!*'() باشد . علاوه بر كاراكترهای فوق ، برخی مرورگرها ممكن است دارای محدوديت های مختص به خود نيز باشند .
در صورتی كه لازم است مجاز بودن كاراكترهای ذخيره شده در query stringبررسی گردد ، می توان از URLencodingاستفاده كرد . با استفاده از ويژگی فوق ، كاراكترهای خاص با دنباله ای از escaped character جايگزين می گردند كه با علامت درصد (%) شروع و به دنبال آن يك عدد دو رقمی مبنای شانزده آورده می شود ( مثلا" spaceبه 20 % تبديل می گردد ) .
از متدهای كلاس HttpServerUtilityمی توان برای encodeاتوماتيك داده استفاده كرد .
كد زير نحوه encodingيك رشته حاوی داده جهت استفاده در query stringرا نشان می دهد . بدين ترتيب ، تمامی كاراكترهای غيرمجاز با دنباله ای از escaped characterجايگزين می گردند .
Dim productName As String = "Test Product" Response.Redirect("newpage.aspx?productName=" & Server.UrlEncode(productName)) |
از كد زير برای decodingدر صفحه مقصد استفاده می گردد .
Dim ID As String = Server.UrlDecode(Request.QueryString("recordID")) |
متاسفانه ، ASP.NET دارای مكانيزم خاصی جهت بررسی و رمزنگاری اتوماتيك query stringنمی باشد . با استفاده از كلاس های متعدد رمزنگاری ارائه شده در دات نت ، می توان رشته های query stringرا رمز و يك سطح مطلوب امنيتی در ارتباط با آنها را ايجاد كرد.
{{Fullname}} {{Creationdate}}
{{Body}}