In standard state management, Session lives until the user logs out, and Cookies live until they expire. But what if you only need data to survive for exactly one single subsequent request? That's the specialized domain of TempData. Mastering it is essential for building robust, duplicate-proof forms using the Post-Redirect-Get pattern.
TempData (backed by ITempDataDictionary) is a very short-lived state management dictionary. Data placed in TempData is persisted only until it is read. Once read, it is instantly marked for deletion at the end of the current request. By default in ASP.NET Core, TempData uses a specialized cookie to store its data, making it stateless on the server side.
Read = Delete. The absolute moment you read a value from TempData["MyKey"], the framework schedules the destruction of that key. If the user refreshes the page, the data will be completely gone.
TempData exists almost exclusively to support the PRG pattern. Without PRG, if a user submits a form (POST) and clicks "Refresh" on their browser, the browser will re-submit the POST request — potentially charging their credit card twice or creating duplicate database records.
public class PaymentController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ProcessCard(PaymentDto model)
{
if (!ModelState.IsValid) return View(model);
// 1. Process the actual payment (POST)
var transactionId = await _paymentService.ChargeAsync(model);
// 2. Load the success message into TempData
TempData["SuccessMessage"] = $"Payment of {model.Amount:C} successful! ID: {transactionId}";
// 3. REDIRECT. This sends a 302 to the browser, forcing a completely new GET request.
return RedirectToAction(nameof(Receipt));
}
[HttpGet]
public IActionResult Receipt()
{
// 4. The GET request reads TempData. It will display correctly.
// Once read, TempData marks "SuccessMessage" for deletion.
return View();
}
}
<!-- Views/Payment/Receipt.cshtml -->
@if (TempData["SuccessMessage"] != null)
{
<!-- If user refreshes this page, TempData is null, so the alert won't show again -->
<div class="alert alert-success">@TempData["SuccessMessage"]</div>
}
<h2>Your Receipt</h2>
Sometimes the "Read = Delete" rule is too aggressive. You might need to read TempData but preserve it for one more request (e.g., if validation fails on the next page and you redirect again). ASP.NET Core offers two methods to manipulate the lifecycle.
Peek(string key) allows you to read a value without marking it for deletion.
public IActionResult FirstPage()
{
// Reads the value, but tells the framework NOT to delete it
string message = TempData.Peek("Notification")?.ToString();
// The data SURVIVES for the next request...
return View();
}
Keep() or Keep(string key) is used when you have already read a value using standard syntax (which marked it for deletion), but you change your mind and want to cancel the deletion.
public IActionResult FirstPage()
{
// 1. Reads the value. It is now MARKED FOR DELETION.
string message = TempData["Notification"]?.ToString();
// 2. Logic happens... we realize we still need it.
if (someConditionFailed)
{
// 3. Cancels the deletion for this specific key
TempData.Keep("Notification");
// Or keep EVERYTHING built up so far
// TempData.Keep();
}
return RedirectToAction("SecondPage");
}
Because TempData is serialized to a cookie by default, it only supports primitive types (string, int, bool, Guid). If you try this, your app will crash:
var user = new User { Name = "John" };
// CRASH: Cannot serialize complex object
TempData["CurrentUser"] = user;
You must serialize the object to JSON precisely before passing it to TempData, then deserialize it on the other side.
// Send
string json = JsonSerializer.Serialize(user);
TempData["CurrentUser"] = json;
// Receive
string jsonStr = TempData["CurrentUser"] as string;
var user = JsonSerializer.Deserialize<User>(jsonStr);
431 Request Header Fields Too Large.Q: "What is the difference between ViewData, ViewBag, and TempData?"
Architect Answer: "The difference is strictly their lifecycle. ViewData (dictionary) and ViewBag (dynamic wrapper over ViewData) only survive for the lifespan of the current HTTP request — they carry data from a Controller directly into its Razor View. Once the HTML is rendered, they are destroyed. TempData, on the other hand, survives for one additional HTTP request. It uses a cookie behind the scenes to bridge the gap between two completely separate HTTP calls, making it the required mechanism for passing notifications during a Post-Redirect-Get flow."