مدیریت State در ASP.NET Core MVC
شناسه پست: 3317
بازدید: 1265

HTTP یک پروتکل stateless (بدون وضعیت) است. بنابراین درخواست‌های HTTP پیام‌های مستقلی هستند که مقادیر کاربر یا state های برنامه را حفظ نمی‌کنند. برای مدیریت state بین request ها باید اقدامات بیشتری را انجام دهیم. در این مقاله قصد داریم رویکردهای مختلفی برای مدیریت وضعیت (state) HTTP که می‌توانیم در برنامه خود استفاده کنیم را بررسی کنیم.

اکیدا توصیه می کنیم سرفصلهای کامل این سری آموزشی را مشاهده کنید: آموزش سریالی ASP.NET Core MVC.

برای دانلود سورس مقاله، روی این لینک کلیک کنید: سورس کد مدیریت state در ASP.NET Core.

ما این مقاله را به قسمتهای زیر تقسیم میکنیم:

Cookie ها، داده ها را در مرورگر کاربر ذخیره میکنند. مرورگرها با هر درخواستی کوکی ها را ارسال می کنند و از این رو اندازه آنها باید در حداقل ممکن باشد. در حالت ایده آل، ما فقط باید یک شناسه را در کوکی ذخیره کرده و داده های مربوطه را با استفاده از برنامه ذخیره کنیم. اکثر مرورگرها اندازه کوکی را به 4096 بایت محدود می کنند و فقط تعداد محدودی کوکی برای هر دامنه در دسترس است.

کاربران به راحتی می توانند یک کوکی را دستکاری یا حذف کنند. کوکی ها همچنین می توانند خود به خود منقضی شوند. بنابراین ما نباید از آنها برای ذخیره اطلاعات حساس استفاده کنیم و نباید به مقادیر آنها اعتماد کرد یا مقادیرشان را بدون اعتبارسنجی مناسب استفاده کرد.

ما اغلب از کوکی‌ها برای شخصی‌سازی محتوا برای یک کاربر شناخته شده استفاده می‌کنیم، به‌خصوص زمانی که یک کاربر بدون احراز هویت را شناسایی می‌کنیم. ما می توانیم از کوکی برای ذخیره برخی از اطلاعات اولیه مانند نام کاربر استفاده کنیم. سپس می‌توانیم از کوکی برای دسترسی به تنظیمات شخصی‌ سازی شده کاربر، مانند رنگ دلخواه theme استفاده کنیم.

یک پروژه جدید بسازیم و یک controller با endpoint هایی برای خواندن و نوشتن مقادیر به داخل کوکی ها ایجاد کنیم:

ورژن Get متد ()Index، مقدار UserName را از کوکی خوانده و آن را به view پاس میدهد.

ما از ورژن Post متد ()Index برای گرفتن مقدار userName از form collection و اختصاص آن به cookie استفاده میکنیم.

برای حذف مقدار cookie باید از RemoveCookie() endpoint استفاده کنیم.

حالا view را ایجاد میکنیم:

اینجا ما مقدار UserName را به عنوان model به داخل View پاس دادیم.

اگر UserName مقداری داشته باشد، به کاربر با نام درون UserName خوشامد می‌گوییم و با قرار دادن یک لینک در زیر آن، به کاربر این امکان را می‌دهیم که با حذف آن از کوکی، مقدار را فراموش کند.

در صورت خالی بودن UserName، یک فیلد input را برای وارد کردن نام و یک دکمه submit برای به روزرسانی کوکی به کاربر نمایش میدهیم.

حالا برنامه را اجرا میکنیم. در ابتدا، برنامه از کاربر میخواهد که یک نام را وارد کند:

زمانیکه نام را وارد میکنیم و update را کلیک میکنیم، برنامه به ما خوش آمد میگوید:

حتی اگر برنامه را stop کنیم و دومرتبه اجرا کنیم، میتوانیم ببینیم که مقدار کوکی حفظ شده است. زمانیکه روی لینک “Forget Me” کلیک کنیم، برنامه cookie را حذف کرده و از ما میخواهد نام را مجددا وارد کنیم.

حالا سوال اینجاست که برنامه کوکی ها را کجا ذخیره میکند؟ آن در مرورگر کاربر ذخیره میشود. برای بررسی مقدار یک کوکی، با زدن دکمه F12، سراغ پنجره ابزار توسعه کروم رفته و سپس به تب Application در آن میرویم. در این Tab میتوانیم ببینیم که مرورگر، کوکی ها را برای هر برنامه ذخیره میکند:

هنگام صدور کوکی ها و داد و ستد با مسائل مربوط به حفظ حریم خصوصی، باید به مقررات عمومی حفاظت از داده های اتحادیه اروپا (GDPR) توجه داشته باشیم. برای اطلاعات بیشتر در مورد API ها و الگوهای ارائه شده توسط ASP.NET Core برای پشتیبانی از الزامات GDPR، به این آدرس مراجعه کنید: پشتیبانی از مقررات حفاظت از داده های عمومی (GDPR) در ASP.NET Core

در این قسمت، ما نحوه استفاده از کوکی ها در برنامه خود را یاد گرفتیم.

Session State

Session State یک مکانیسم ASP.NET Core برای ذخیره داده های کاربر در حالی که کاربر برنامه را browse (گشت و گذار در برنامه) می کند است. Session State از یک حافظه که توسط برنامه نگهداری می‌شود استفاده می‌کند تا داده‌ها را بین درخواست‌های کلاینت حفظ کند. ما بهتر است داده های حیاتی برنامه را در پایگاه داده کاربر ذخیره کنیم و در صورت لزوم، آنها را در یک session، تنها با هدف بهینه سازی performance برنامه cache کنیم.

ASP.NET Core با ارائه یک کوکی به کلاینت که شامل یک session ID است، session state را در خود نگهداری میکند. مرورگر با هر request، این کوکی را به برنامه میفرستد. سپس برنامه از session ID موجود در کوکی برای واکشی داده های session استفاده میکند.

هنگام کار با وضعیت session state، باید موارد زیر را در نظر داشته باشیم:

  • یک Session cookie مختص session مرورگر است
  • وقتیکه یک session مرورگر تمام میشود، آن، session cookie موردنظر خود را حذف میکند
  • اگر برنامه یک کوکی را برای یک session منقضی شده دریافت کند، سپس یک session جدید ایجاد می کند که از همان session cookie استفاده می کند
  • یک برنامه، session های خالی را نگه نمی دارد
  • برنامه، یک session را پس از آخرین request، برای مدت زمان محدودی در خود حفظ می کند. برنامه یا session timeout را برای session تنظیم می کند یا از مقدار پیش فرض 20 دقیقه برای آن session استفاده می کند
  • Session state برای ذخیره داده های کاربر که مختص یک session خاص هستند اما نیازی به ذخیره سازی دائمی در میان session ها ندارند ایده آل است.
  • یک برنامه، داده های ذخیره شده در یک session را یا با فراخوانی دستور ISession.Clear یا زمانیکه expire ،session شود، حذف میکند
  • هیچ مکانیزم پیش‌فرضی برای اطلاع دادن به برنامه وجود ندارد مبنی بر اینکه یک کلاینت مرورگر را بسته یا session cookie را حذف کرده یا منقضی شده است

یک مثال از Session State

ما باید session state را قبل از اینکه از آن استفاده کنیم، آن را تنظیم کنیم. این تنظیم در متد ()ConfigureServices در کلاس Startup.cs انجام میشود:

سپس باید session state را در متد ()Configure در همین کلاس Startup.cs فعال کنیم:

اینجا ترتیب تنظیمات مهم است و ما باید ()UseSession را قبل از ()UseMVC فراخوانی کنیم.

حال یک controller با endpoint های مربوط به خواندن و نوشتن از session ایجاد کنیم:

متد ()Index، مقادیر را داخل session قرار میدهد و متد ()Get مقادیر را از session خوانده و آن را به view پاس میدهد.

اجازه دهید با راست کلیک کردن بر روی متد ()Get و استفاده از گزینه “Add View”، به طور خودکار یک view را برای نمایش مقادیر model تولید کنیم.

حالا برنامه را اجرا کنیم و به welcome/ برویم.

این متد Index را فراخوانی کرده و مقادیر session را تنظیم میکند.

حالا به welcome/get/ برویم:

اینجا مقادیر را از session دریافت و آن را روی صفحه نمایش میدهد.

در این قسمت، ما نحوه تنظیم داده ها در session و دریافت آنها از session را یاد گرفتیم.

Query string ها

ما می‌توانیم مقدار محدودی داده را از یک request به request دیگر با اضافه کردن آن به رشته کوئری request جدید منتقل کنیم. این شیوه، برای گرفتن state به صورت مداوم مفید است و امکان اشتراک گذاری لینکها را به حالت embed شده فراهم می کند.

حال یک متد جدید در WelcomeController اضافه میکنیم:

Model binding داده ها را از HTTP request ها به پارامترهای map ،action method میکند. بنابراین اگر ما، مقادیر را برای name و age به صورت form value ها یا route value ها یا query string ها ارائه دهیم، میتوانیم آنها را به پارامترهای action method خود bind کنیم.

بیاید مانند قسمت قبلی، برای نمایش مقادیر model، یک view را به طور خودکار تولید کنیم.

حالا با پاس دادن پارامترهای query string، این متد را فراخوانی میکنیم:

ما میتوانیم هر دو مقادیر name و age را از query string دریافت کرده و آن را بر روی صفحه نمایش دهیم.

از آنجایی که این رشته کوئری ها در URL عمومی هستند، نباید از آنها برای داده های حساس استفاده کنیم.

علاوه بر اشتراک‌گذاری ناخواسته، گنجاندن داده‌ها در query string ها، برنامه ما را در برابر حملات  Cross-Site Request Forgery (CSRF) آسیب‌پذیر میسازد، که می‌تواند کاربران را فریب دهد تا هنگام احراز هویت، از سایت‌های مخرب بازدید کنند. سپس مهاجمان می توانند داده های کاربر را بدزدند یا از طرف کاربر اقدامات مخربی انجام دهند. برای اطلاعات بیشتر در مورد محافظت از برنامه خود در برابر حملات CSRF، جلوگیری از حملات  Cross-Site Request Forgery (XSRF/CSRF) را بازدید کنید.

در این قسمت، ما نحوه پاس دادن پارامترها به عنوان query string و نحوه خواندن آنها در برنامه خود را یاد گرفتیم.

Hidden Field ها

ما میتوانیم داده ها را در فیلدهای hidden فرم ذخیره کنیم و در request بعدی، آن را ارسال کنیم. بعضی اوقات ما نیاز داریم برخی از داده ها، بدون اینکه در صفحه نمایش داده شوند، در سمت کلاینت ذخیره شوند. سپس زمانیکه کاربر اقدامی را انجام داد، ما باید آن داده‌ها را به سمت سرور پاس دهیم. این یک سناریوی رایج در بسیاری از اپلیکیشنها است و فیلدهای پنهان، راه حل خوبی را برای این امر ارائه می دهند.

بیایید دو متد در WelcomeController اضافه کنیم:

ورژن GET متد ()SetHiddenValue یک آبجکت از نوع user ایجاد میکند و آن را به view میفرستد.

ما از ورژن POST متد ()SetHiddenValue برای خواندن مقدار از یک hidden field به نام Id از داخل FormCollection استفاده میکنیم.

در View، میتوانیم یک hidden field ایجاد کنیم و مقدار Id از Model را به آن bind کنیم:

سپس میتوانیم از یک دکمه submit برای submit کردن form استفاده کنیم.

حالا برنامه را اجرا کنیم و به Welcome/SetHiddenFieldValue/ برویم:

با بررسی سورس صفحه، می‌توانیم ببینیم که یک hidden field در صفحه با مقدار Id ایجاد شده است:

حالا یک breakpoint در متد POST میگذاریم و روی دکمه submit کلیک میکنیم. ما میتوانیم اینجا مقدار Id را از FormCollection دریافت کنیم:

از آنجایی که client به طور بالقوه می تواند داده ها را دستکاری کند، برنامه ما همیشه باید داده های ذخیره شده در فیلدهای مخفی را مجدداً اعتبارسنجی کند.

در این قسمت، ما نحوه ذخیره داده ها در  hidden field ها و نحوه دریافت مقادیر از آنها را یاد گرفتیم.

TempData

ASP.NET Core ویژگی TempData را معرفی میکند که می تواند برای ذخیره داده ها تا زمانی که خوانده شود استفاده شود. برای بررسی داده ها بدون حذف می توانیم از متدهای ()Keep و ()Peek استفاده کنیم. TempData به ویژه زمانی مفید است که ما به داده ها برای بیش از یک request نیاز داشته باشیم. ما میتوانیم از controller ها و view ها به آنها دسترسی داشته باشیم.

TempData توسط ارائه دهندگان TempData یا با استفاده از cookie ها یا session state پیاده سازی می شود.

یک مثال از TempData 

یک کنترلر با 3 endpoint ایجاد کنیم. در متد ()First، یک مقدار را به داخل TempData ست کنیم. سپس سعی کنیم آن را در متدهای ()Second و ()Third بخوانیم:

برنامه را با قرار دادن breakpoint در متدهای ()Second و ()Third اجرا میکنیم.

میتوانیم ببینیم که TempData در درخواست ()First ست میشود و زمانیکه سعی میکنیم در متد ()Second به آن دسترسی داشته باشیم، موجود خواهد بود. اما مقدار درون TempData، از آنجایی که تا زمانی که خوانده نشده نگه داشته میشود، به همین خاطر زمانیکه سعی میکنیم در متد ()Third به آن دسترسی داشته باشیم، مقدار درون آن موجود نخواهد بود. یعنی چون در متد ()Second مقدار درون آن را خواندیم، بعد آن دیگر مقدار درون آن از بین رفت.

حالا کد دسترسی به TempData را از متدهای controller به view ها انتقال میدهیم.

یک view برای اکشن متد ()Second ایجاد میکنیم:

به همین ترتیب، یک view نیز برای اکشن متد ()Third ایجاد میکنیم:

برنامه را اجرا کنیم و به first ،/second/ و third/ برویم:

میبینیم که TempData زمانیکه برای اولین بار خوانده میشود موجود خواهد بود و سپس مقدار خودش را از دست میدهد. حالا اگر بخواهیم که مقدار TempData بعد از آنکه آن را مورد دسترسی قرار دادیم باقی بماند باید چه کار کنیم؟ دو راه برای انجام این کار وجود دارد:

  • TempData.Keep()/TempData.Keep(string key) – متد TempData.Keep(string key) مقدار مربوط به TempData با key پاس داده شده را حفظ میکند. اگر کلیدی پاس داده نشده باشد، تمام مقادیر در TempData را حفظ میکند.
  • TempData.Peek(string key) – این متد، مقدار کلید پاس داده شده به TempData را گرفته و برای request بعدی آن را حفظ میکند.

اجازه دهید تا second view خود را با یکی از این متدها کمی تغییر دهیم:

یا

برنامه را اجرا کنیم و به first ،/second/ و third/ برویم.

میتوانیم ببینیم که مقدار TempData حتی بعد از اینکه در صفحه second خوانده میشود در صفحه third باقی میماند. عالی!

در این قسمت، ما نحوه پاس دادن مقادیر از اکشن متد یک controller به اکشن متد دیگر یا به یک view را با استفاده از TempData یاد گرفتیم.

پاس دادن داده ها به View ها

برای پاس دادن داده ها از controller به view ها، میتوانیم از دو رویکرد استفاده کنیم:

  • داده های Strongly Typed
  • داده های Weakly Typed

داده های Strongly Typed

در این رویکرد، ما یک model را از controller به view پاس میدهیم. ما این را با جزییات در قسمت اول این سری آموزشی شرح داده ایم:

مزیت این متد این است که ما یک model از نوع strongly typed را برای کار داخل view میگیریم:

سپس میتوانیم از view به خصوصیات درون model دسترسی داشته باشیم:

داده های Weakly Typed

دو رویکرد برای پاس دادن داده weakly typed به view ها وجود دارد:

  • ViewData
  • ViewBag

ViewData یک آبجکت از نوع dictionary است که میتوانیم با استفاده از یک کلید، مقادیر را get یا set کنیم. ViewData نمایانگر نمونه ای از کلاس ViewDataDictionary میباشد.

یک controller و اکشن متد ایجاد کنیم و مقدار UserId را داخل ViewData ست کنیم:

حالا سعی کنیم مقدار userId را داخل View مورد دسترسی قرار دهیم:

برنامه را اجرا کنیم و به viewdata/ برویم:

میبینیم که مقدار UserId از ViewData خوانده شده و بر روی صفحه نمایش داده شده است.

ViewBag

ViewBag شبیه ViewData است با این تفاوت که یک آبجکت از نوع dynamic است و ما میتوانیم داده ها را بدون تبدیل کردن به یک آبجکت strongly typed به آن اضافه کنیم. به عبارتی دیگر، ViewBag فقط یک dynamic wrapper برای ViewData است.

یک controller و اکشن متد ایجاد کنیم و چند مقدار در ViewBag ست کنیم:

در View، آنها را مورد دسترسی قرار دهیم و مقادریشان را نمایش دهیم:

برنامه را اجرا کنیم و به viewbag/ برویم:

تمام!

در این قسمت، ما نحوه ست کردن مقادیر در ViewData و ViewBag و نحوه نمایش آنها در View ها را یاد گرفتیم.

نتیجه گیری

در این مقاله، ما موضوعات زیر را یاد گرفتیم:

  • ذخیره داده های مختص کاربر در کوکی های مرورگر
  • استفاده از Session State برای ذخیره داده های مرتبط به یک session کاربر
  • پاس دادن داده ها بین request ها با استفاده از Query String ها
  • استفاده از Hidden Field ها برای ذخیره داده هایی که روی صفحه نمایش داده نمیشوند
  • استفاده از TempData برای پاس دادن داده ها از یک action method در controller به action method دیگر یا به یک View
  • پاس دادن داده ها از یک Controller به یک View با استفاده از داده های strongly typed و weakly typed مانند ViewData و ViewBag

در قسمت بعدی این سری آموزشی، قابلیتهای مسیریابی (routing) در ASP.NET Core MVC را یاد میگیریم.

نویسنده

امید عباسی
من امید عباسی هستم. سالهاست که در زمینه برنامه نویسی با تکنولوژی دات نت فعالیت میکنم و عاشق این هستم که تجربیات و دانش خودم را در این زمینه با دیگران به اشتراک بزارم. خیلی دوست دارم که نظر و انتقاد خودتون رو در مورد این نوشته برای من بنویسید تا بتونم در آینده، مطالب بهتر و ارزشمندتری را برای شما فراهم کنم. در صورت داشتن هرگونه سوال هم در قسمت دیدگاه ها میتونید با بنده در ارتباط باشید