HTTP یک پروتکل stateless (بدون وضعیت) است. بنابراین درخواستهای HTTP پیامهای مستقلی هستند که مقادیر کاربر یا state های برنامه را حفظ نمیکنند. برای مدیریت state بین request ها باید اقدامات بیشتری را انجام دهیم. در این مقاله قصد داریم رویکردهای مختلفی برای مدیریت وضعیت (state) HTTP که میتوانیم در برنامه خود استفاده کنیم را بررسی کنیم.
اکیدا توصیه می کنیم سرفصلهای کامل این سری آموزشی را مشاهده کنید: آموزش سریالی ASP.NET Core MVC.
برای دانلود سورس مقاله، روی این لینک کلیک کنید: سورس کد مدیریت state در ASP.NET Core.
ما این مقاله را به قسمتهای زیر تقسیم میکنیم:
Cookie ها
Cookie ها، داده ها را در مرورگر کاربر ذخیره میکنند. مرورگرها با هر درخواستی کوکی ها را ارسال می کنند و از این رو اندازه آنها باید در حداقل ممکن باشد. در حالت ایده آل، ما فقط باید یک شناسه را در کوکی ذخیره کرده و داده های مربوطه را با استفاده از برنامه ذخیره کنیم. اکثر مرورگرها اندازه کوکی را به 4096 بایت محدود می کنند و فقط تعداد محدودی کوکی برای هر دامنه در دسترس است.
کاربران به راحتی می توانند یک کوکی را دستکاری یا حذف کنند. کوکی ها همچنین می توانند خود به خود منقضی شوند. بنابراین ما نباید از آنها برای ذخیره اطلاعات حساس استفاده کنیم و نباید به مقادیر آنها اعتماد کرد یا مقادیرشان را بدون اعتبارسنجی مناسب استفاده کرد.
ما اغلب از کوکیها برای شخصیسازی محتوا برای یک کاربر شناخته شده استفاده میکنیم، بهخصوص زمانی که یک کاربر بدون احراز هویت را شناسایی میکنیم. ما می توانیم از کوکی برای ذخیره برخی از اطلاعات اولیه مانند نام کاربر استفاده کنیم. سپس میتوانیم از کوکی برای دسترسی به تنظیمات شخصی سازی شده کاربر، مانند رنگ دلخواه theme استفاده کنیم.
یک مثال از Cookie
یک پروژه جدید بسازیم و یک controller با endpoint هایی برای خواندن و نوشتن مقادیر به داخل کوکی ها ایجاد کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class HomeController : Controller { public IActionResult Index() { //read cookie from Request object string userName = Request.Cookies["UserName"]; return View("Index", userName); } [HttpPost] public IActionResult Index(IFormCollection form) { string userName = form["userName"].ToString(); //set the key value in Cookie CookieOptions option = new CookieOptions(); option.Expires = DateTime.Now.AddMinutes(10); Response.Cookies.Append("UserName", userName, option); return RedirectToAction(nameof(Index)); } public IActionResult RemoveCookie() { //Delete the cookie Response.Cookies.Delete("UserName"); return View("Index"); } } |
ورژن Get متد ()Index، مقدار UserName را از کوکی خوانده و آن را به view پاس میدهد.
ما از ورژن Post متد ()Index برای گرفتن مقدار userName از form collection و اختصاص آن به cookie استفاده میکنیم.
برای حذف مقدار cookie باید از RemoveCookie() endpoint استفاده کنیم.
حالا view را ایجاد میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@model string @{ ViewData["Title"] = "Home Page"; } @if (!string.IsNullOrWhiteSpace(Model)) { @:<div>Welcome back, @Model</div> @Html.ActionLink("Forget Me", "RemoveCookie") } else { @: <form asp-action="Index"> <span>Hey, seems like it's your first time here!</span><br /> <label>Please provide your name:</label> @Html.TextBox("userName") <div class="form-group"> <input type="submit" value="Update" class="btn btn-primary" /> </div> </form> } |
اینجا ما مقدار UserName را به عنوان model به داخل View پاس دادیم.
اگر UserName مقداری داشته باشد، به کاربر با نام درون UserName خوشامد میگوییم و با قرار دادن یک لینک در زیر آن، به کاربر این امکان را میدهیم که با حذف آن از کوکی، مقدار را فراموش کند.
در صورت خالی بودن UserName، یک فیلد input را برای وارد کردن نام و یک دکمه submit برای به روزرسانی کوکی به کاربر نمایش میدهیم.
حالا برنامه را اجرا میکنیم. در ابتدا، برنامه از کاربر میخواهد که یک نام را وارد کند:
زمانیکه نام را وارد میکنیم و update را کلیک میکنیم، برنامه به ما خوش آمد میگوید:
حتی اگر برنامه را stop کنیم و دومرتبه اجرا کنیم، میتوانیم ببینیم که مقدار کوکی حفظ شده است. زمانیکه روی لینک “Forget Me” کلیک کنیم، برنامه cookie را حذف کرده و از ما میخواهد نام را مجددا وارد کنیم.
بررسی کردن 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 انجام میشود:
1 |
services.AddSession(); |
سپس باید session state را در متد ()Configure در همین کلاس Startup.cs فعال کنیم:
1 |
app.UseSession(); |
اینجا ترتیب تنظیمات مهم است و ما باید ()UseSession را قبل از ()UseMVC فراخوانی کنیم.
حال یک controller با endpoint های مربوط به خواندن و نوشتن از session ایجاد کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class WelcomeController : Controller { public IActionResult Index() { HttpContext.Session.SetString("Name", "John"); HttpContext.Session.SetInt32("Age", 32); return View(); } public IActionResult Get() { User newUser = new User() { Name = HttpContext.Session.GetString("Name"), Age = HttpContext.Session.GetInt32("Age").Value }; return View(newUser); } } |
متد ()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 اضافه میکنیم:
1 2 3 4 5 6 7 8 9 |
public IActionResult GetQueryString(string name, int age) { User newUser = new User() { Name = name, Age = age }; return View(newUser); } |
Model binding داده ها را از HTTP request ها به پارامترهای map ،action method میکند. بنابراین اگر ما، مقادیر را برای name
و age
به صورت form value ها یا route value ها یا query string ها ارائه دهیم، میتوانیم آنها را به پارامترهای action method خود bind کنیم.
بیاید مانند قسمت قبلی، برای نمایش مقادیر model، یک view را به طور خودکار تولید کنیم.
حالا با پاس دادن پارامترهای query string، این متد را فراخوانی میکنیم:
1 |
/welcome/getquerystring?name=John&age=31 |
ما میتوانیم هر دو مقادیر 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 اضافه کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[HttpGet] public IActionResult SetHiddenFieldValue() { User newUser = new User() { Id = 101, Name = "John", Age = 31 }; return View(newUser); } [HttpPost] public IActionResult SetHiddenFieldValue(IFormCollection keyValues) { var id = keyValues["Id"]; return View(); } |
ورژن GET
متد ()SetHiddenValue یک آبجکت از نوع user ایجاد میکند و آن را به view میفرستد.
ما از ورژن POST
متد ()SetHiddenValue برای خواندن مقدار از یک hidden field به نام Id
از داخل FormCollection استفاده میکنیم.
در View، میتوانیم یک hidden field ایجاد کنیم و مقدار Id
از Model را به آن bind کنیم:
1 |
@Html.HiddenFor(model => model.Id) |
سپس میتوانیم از یک دکمه submit برای submit کردن form استفاده کنیم.
1 |
<input type="submit" value="Submit" />| |
حالا برنامه را اجرا کنیم و به Welcome/SetHiddenFieldValue/ برویم:
با بررسی سورس صفحه، میتوانیم ببینیم که یک hidden field در صفحه با مقدار Id ایجاد شده است:
1 |
<input id="Id" name="Id" type="hidden" value="101"> |
حالا یک 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 بخوانیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class TempDataController : Controller { public IActionResult First() { TempData["UserId"] = 101; return View(); } public IActionResult Second() { var userId = TempData["UserId"] ?? null; return View(); } public IActionResult Third() { var userId = TempData["UserId"] ?? null; return View(); } } |
برنامه را با قرار دادن breakpoint در متدهای ()Second و ()Third اجرا میکنیم.
میتوانیم ببینیم که TempData
در درخواست ()First ست میشود و زمانیکه سعی میکنیم در متد ()Second به آن دسترسی داشته باشیم، موجود خواهد بود. اما مقدار درون TempData
، از آنجایی که تا زمانی که خوانده نشده نگه داشته میشود، به همین خاطر زمانیکه سعی میکنیم در متد ()Third به آن دسترسی داشته باشیم، مقدار درون آن موجود نخواهد بود. یعنی چون در متد ()Second مقدار درون آن را خواندیم، بعد آن دیگر مقدار درون آن از بین رفت.
حالا کد دسترسی به TempData
را از متدهای controller به view ها انتقال میدهیم.
یک view برای اکشن متد ()Second ایجاد میکنیم:
1 2 3 4 5 6 7 |
@{ ViewData["Title"] = "Second"; var userId = TempData["UserId"]?.ToString(); } <h1>Second</h1> User Id : @userId |
به همین ترتیب، یک view نیز برای اکشن متد ()Third ایجاد میکنیم:
1 2 3 4 5 6 7 |
@{ ViewData["Title"] = "Third"; var userId= TempData["UserId"]?.ToString(); } <h1>Third</h1> User Id : @userId |
برنامه را اجرا کنیم و به 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 خود را با یکی از این متدها کمی تغییر دهیم:
1 2 |
var userId = TempData["UserId"]?.ToString(); TempData.Keep(); |
یا
1 |
var userId = TempData.Peek("UserId")?.ToString(); |
برنامه را اجرا کنیم و به first ،/second
/ و third/ برویم.
میتوانیم ببینیم که مقدار TempData
حتی بعد از اینکه در صفحه second خوانده میشود در صفحه third باقی میماند. عالی!
در این قسمت، ما نحوه پاس دادن مقادیر از اکشن متد یک controller به اکشن متد دیگر یا به یک view را با استفاده از TempData یاد گرفتیم.
پاس دادن داده ها به View ها
برای پاس دادن داده ها از controller به view ها، میتوانیم از دو رویکرد استفاده کنیم:
- داده های Strongly Typed
- داده های Weakly Typed
داده های Strongly Typed
در این رویکرد، ما یک model را از controller به view پاس میدهیم. ما این را با جزییات در قسمت اول این سری آموزشی شرح داده ایم:
1 |
return View(model); |
مزیت این متد این است که ما یک model از نوع strongly typed را برای کار داخل view میگیریم:
1 |
@model BookStore.Models.Book |
سپس میتوانیم از view به خصوصیات درون model دسترسی داشته باشیم:
1 |
@Model.Title |
داده های Weakly Typed
دو رویکرد برای پاس دادن داده weakly typed به view ها وجود دارد:
- ViewData
- ViewBag
ViewData یک آبجکت از نوع dictionary است که میتوانیم با استفاده از یک کلید، مقادیر را get یا set کنیم. ViewData
نمایانگر نمونه ای از کلاس ViewDataDictionary
میباشد.
یک controller و اکشن متد ایجاد کنیم و مقدار UserId
را داخل ViewData ست کنیم:
1 2 3 4 5 6 7 8 |
public class ViewDataController : Controller { public IActionResult Index() { ViewData["UserId"] = 101; return View(); } } |
حالا سعی کنیم مقدار userId
را داخل View مورد دسترسی قرار دهیم:
1 2 3 4 5 6 7 8 |
@{ ViewData["Title"] = "Index"; var userId = ViewData["UserId"]?.ToString(); } <h1>ViewData</h1> User Id : @userId |
برنامه را اجرا کنیم و به viewdata/ برویم:
میبینیم که مقدار UserId
از ViewData
خوانده شده و بر روی صفحه نمایش داده شده است.
ViewBag
ViewBag
شبیه ViewData
است با این تفاوت که یک آبجکت از نوع dynamic است و ما میتوانیم داده ها را بدون تبدیل کردن به یک آبجکت strongly typed به آن اضافه کنیم. به عبارتی دیگر، ViewBag
فقط یک dynamic wrapper برای ViewData است.
یک controller و اکشن متد ایجاد کنیم و چند مقدار در ViewBag ست کنیم:
1 2 3 4 5 6 7 8 9 10 11 |
public class ViewBagController : Controller { public IActionResult Index() { ViewBag.UserId = 101; ViewBag.Name = "John"; ViewBag.Age = 31; return View(); } } |
در View، آنها را مورد دسترسی قرار دهیم و مقادریشان را نمایش دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ ViewData["Title"] = "Index"; var userId = ViewBag.UserId; var name = ViewBag.Name; var age = ViewBag.Age; } <h1>ViewBag</h1> User Id : @userId<br /> Name : @name<br /> Age : @age<br /> |
برنامه را اجرا کنیم و به 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 را یاد میگیریم.