Sign in to track progress and bookmarks.
HTTP is stateless. Every request is a stranger. So how does your application remember a success notification after a form submission redirects the user? That's the exact problem TempData was built to solve β and understanding it deeply separates a junior developer from a production-ready engineer.
TempData is a short-lived, dictionary-based storage mechanism in ASP.NET Core MVC that allows you to pass data from one HTTP request to the very next request β and then it self-destructs. Think of it as a sticky note you hand to the next person in line, and once they read it, it dissolves.
Internally, TempData implements ITempDataDictionary, which inherits from IDictionary<string, object>. It stores values as object, meaning you must cast back to the original type when reading.
The real-world reason TempData exists is the Post-Redirect-Get (PRG) Pattern. Consider this real production scenario:
/orders (GET request) β this prevents duplicate form submissions on refreshThe Problem: Step 3 triggers a brand-new HTTP request. ViewData and ViewBag from Step 2 are completely gone β they don't survive across requests. You need a mechanism to pass that success message across the redirect boundary.
The Solution: TempData. It stores the data in a cookie (default) or session, and it survives precisely until it is read in the next request.
ASP.NET Core provides two TempData Providers β the mechanism that physically stores and retrieves TempData between requests:
AddSession() and UseSession() middleware setup// Program.cs β Switching to Session-based TempData
builder.Services.AddControllersWithViews()
.AddSessionStateTempDataProvider(); // Switch from cookie to session
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
var app = builder.Build();
app.UseSession(); // Must come BEFORE UseRouting
app.UseRouting();
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
// OrdersController.cs β Production-grade POST handler
public class OrdersController : Controller
{
private readonly IOrderService _orderService;
private readonly ILogger<OrdersController> _logger;
public OrdersController(IOrderService orderService, ILogger<OrdersController> logger)
{
_orderService = orderService;
_logger = logger;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> PlaceOrder(OrderViewModel model)
{
if (!ModelState.IsValid)
return View("Checkout", model);
try
{
var orderId = await _orderService.CreateOrderAsync(model);
// Store notification message in TempData β survives the redirect
TempData["Notification"] = $"Order #{orderId} placed successfully! π";
TempData["NotificationType"] = "success";
_logger.LogInformation("Order {OrderId} created by user {UserId}", orderId, User.Identity.Name);
return RedirectToAction(nameof(Confirmation), new { id = orderId });
}
catch (InsufficientStockException ex)
{
TempData["Notification"] = $"Item '{ex.ProductName}' is out of stock.";
TempData["NotificationType"] = "danger";
return RedirectToAction(nameof(Index));
}
}
public IActionResult Confirmation(int id)
{
// TempData["Notification"] is automatically available in the view
var order = _orderService.GetOrderById(id);
return View(order);
}
}
<!-- _Layout.cshtml β Global notification renderer -->
@if (TempData["Notification"] != null)
{
var alertType = TempData["NotificationType"]?.ToString() ?? "info";
<div class="alert alert-@alertType alert-dismissible fade show" role="alert">
<strong>@(alertType == "success" ? "β
" : "β οΈ")</strong>
@TempData["Notification"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
// ProductsController.cs β Clean, type-safe approach
public class ProductsController : Controller
{
// Decorated properties auto-sync with TempData dictionary
[TempData]
public string StatusMessage { get; set; }
[TempData]
public string StatusType { get; set; }
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
{
var product = await _repository.GetByIdAsync(id);
if (product == null)
{
StatusMessage = "Product not found.";
StatusType = "warning";
return RedirectToAction(nameof(Index));
}
await _repository.DeleteAsync(id);
StatusMessage = $"Product '{product.Name}' has been permanently deleted.";
StatusType = "danger";
return RedirectToAction(nameof(Index));
}
}
// TempData can only store primitive types natively.
// For complex objects, serialize to JSON first:
using System.Text.Json;
// SETTING complex data
var orderSummary = new OrderSummary { OrderId = 4521, Total = 149.99m, ItemCount = 3 };
TempData["OrderSummary"] = JsonSerializer.Serialize(orderSummary);
// READING complex data (in the next request)
if (TempData["OrderSummary"] is string json)
{
var summary = JsonSerializer.Deserialize<OrderSummary>(json);
// Use summary.OrderId, summary.Total, etc.
}
// PRO TIP: Create extension methods for cleaner usage
public static class TempDataExtensions
{
public static void Set<T>(this ITempDataDictionary tempData, string key, T value)
=> tempData[key] = JsonSerializer.Serialize(value);
public static T? Get<T>(this ITempDataDictionary tempData, string key)
{
if (tempData.TryGetValue(key, out var obj) && obj is string json)
return JsonSerializer.Deserialize<T>(json);
return default;
}
}
By default, reading TempData marks it for deletion. Sometimes you need to read without consuming, or extend its life for one more request:
Reads the value but does NOT mark it for deletion. The data remains available for the subsequent request.
// Value survives to the next request
var msg = TempData.Peek("StatusMessage");
// msg has the value, but TempData["StatusMessage"]
// will STILL be available in the next request
If you've already read the value (which marks it for deletion), calling Keep() cancels the deletion for the next request.
// Step 1: Read (auto-marks for deletion)
var msg = TempData["StatusMessage"];
// Step 2: Cancel deletion β keep for one more request
TempData.Keep("StatusMessage");
// Or keep ALL TempData items
TempData.Keep();
| Feature | ViewData | ViewBag | TempData |
|---|---|---|---|
| Type | Dictionary<string, object> | dynamic wrapper | Dictionary<string, object> |
| Scope | Current request only | Current request only | Current + next request |
| Survives Redirect? | β No | β No | β Yes |
| Type-Safe? | No (requires casting) | No (dynamic) | No (requires casting) |
| Storage | In-memory (request) | In-memory (request) | Cookie or Session |
| Best For | Page titles, metadata | Quick ad-hoc data | Flash messages after redirect |
| Read Behavior | Unlimited reads | Unlimited reads | Auto-deleted after first read |
[TempData] attribute to avoid magic string keys_Layout.cshtml for global visibilityTempData auto-deletes on read β use Peek/Keep if neededIf you store too much data in TempData with the default cookie provider, the response header exceeds the browser's cookie limit (~4KB). The request will silently fail with no error message β the data just vanishes. In production, always validate payload size or switch to the Session provider for larger payloads.
Q: "When would you use TempData instead of Session in a production application?"
Architect Answer: "I use TempData exclusively for ephemeral notification messages following the Post-Redirect-Get pattern β success alerts, form validation summaries, or one-time warnings. TempData self-destructs after being read, so I never have to worry about cleanup or stale data. For anything that needs to persist across multiple requests β like a multi-step wizard, a shopping cart, or user preferences β I use Session backed by a distributed cache like Redis. The key architectural distinction is: TempData is for one-time messages, Session is for multi-request state, and a database is for persistent state."
Quizzes linked to this courseβpass to earn certificates.
On this page
1. WHAT is TempData? 2. WHY Do We Need TempData? The PRG Problem 3. HOW TempData Works Under the Hood 4. REAL-TIME PRODUCTION EXAMPLES 5. Keep() and Peek() β Controlling TempData Lifetime 6. ViewData vs ViewBag vs TempData β The Complete Comparison 7. Best Practices & Production Guidelines 8. Interview Mastery