ASP.NET Core MVC Mastery
Lesson 17 of 58 29% of course

TempData

19 Β· 8 min Β· 5/23/2026

Sign in to track progress and bookmarks.

TempData in ASP.NET Core MVC β€” The Complete Production Guide

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.

1. WHAT is TempData?

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 Core Mental Model
  • ViewData / ViewBag β†’ Live for the current request only (Controller β†’ View)
  • TempData β†’ Survives exactly ONE redirect (Controller β†’ Redirect β†’ Next Controller/View)
  • Session β†’ Persists across many requests until timeout or explicit removal

2. WHY Do We Need TempData? The PRG Problem

The real-world reason TempData exists is the Post-Redirect-Get (PRG) Pattern. Consider this real production scenario:

  1. User fills out an order form and clicks Submit (POST request)
  2. Server processes the order successfully
  3. Server redirects user to /orders (GET request) β€” this prevents duplicate form submissions on refresh
  4. The orders page needs to show: "Order #4521 placed successfully!"

The 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.

3. HOW TempData Works Under the Hood

ASP.NET Core provides two TempData Providers β€” the mechanism that physically stores and retrieves TempData between requests:

Cookie-Based Provider (Default)
  • TempData values are serialized to JSON, then encrypted using ASP.NET Core's Data Protection API
  • Stored as a cookie on the client's browser
  • No server-side state needed β€” perfect for web farms and load balancers
  • Limitation: Cookie size limit (~4KB). Don't store large objects
Session-Based Provider
  • Stores TempData on the server-side in session storage
  • Requires AddSession() and UseSession() middleware setup
  • Can handle larger payloads than cookies
  • Trade-off: Requires sticky sessions in web farms unless using distributed session (Redis)

Switching to Session-Based TempData Provider:

// 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?}");

4. REAL-TIME PRODUCTION EXAMPLES

Example 1: E-Commerce Order Notification (PRG Pattern)

// 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);
    }
}

Rendering Notifications Globally (in _Layout.cshtml):

<!-- _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>
}

Example 2: Using [TempData] Attribute β€” Eliminate Magic Strings

// 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));
    }
}

Example 3: Passing Complex Objects via JSON Serialization

// 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;
    }
}

5. Keep() and Peek() β€” Controlling TempData Lifetime

By default, reading TempData marks it for deletion. Sometimes you need to read without consuming, or extend its life for one more request:

Peek(key) β€” Read Without Consuming

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
Keep(key) β€” Retain After Reading

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();

6. ViewData vs ViewBag vs TempData β€” The Complete Comparison

FeatureViewDataViewBagTempData
TypeDictionary<string, object>dynamic wrapperDictionary<string, object>
ScopeCurrent request onlyCurrent request onlyCurrent + next request
Survives Redirect?❌ No❌ Noβœ… Yes
Type-Safe?No (requires casting)No (dynamic)No (requires casting)
StorageIn-memory (request)In-memory (request)Cookie or Session
Best ForPage titles, metadataQuick ad-hoc dataFlash messages after redirect
Read BehaviorUnlimited readsUnlimited readsAuto-deleted after first read

7. Best Practices & Production Guidelines

βœ… DO
  • Use TempData only for flash notifications after PRG redirects
  • Use the [TempData] attribute to avoid magic string keys
  • Create extension methods for complex object serialization
  • Place notification rendering in _Layout.cshtml for global visibility
  • Keep payloads small (< 4KB for cookie provider)
❌ DON'T
  • Don't use TempData as a substitute for Session or database storage
  • Don't store large objects or lists β€” serialize to JSON if needed
  • Don't store sensitive data (passwords, tokens) β€” it's in a cookie!
  • Don't rely on TempData for multi-step wizards β€” use Session or DB instead
  • Don't forget that TempData auto-deletes on read β€” use Peek/Keep if needed
Production Gotcha: Cookie Size Overflow

If 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.

8. Interview Mastery

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."

Test your knowledge

Quizzes linked to this courseβ€”pass to earn certificates.

Browse all quizzes
ASP.NET Core MVC Mastery

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
1. Core Framework
Introduction to ASP.NET Core MVC
MODULE 1: INTRODUCTION & ENVIRONMENT SETUP
Microsoft Web Stack Overview Evolution of ASP.NET Environment Setup
2. View Engine
Layouts & Partial Views in Razor
MODULE 2: .NET CORE FUNDAMENTALS
Core Concepts Project Structure Startup Flow Middleware Pipeline
MODULE 3: ASP.NET CORE BASICS
Creating Project CLI Commands wwwroot & Static Files
MODULE 4: MVC FUNDAMENTALS
MVC Architecture Dependency Injection (DI) Service Lifetimes
MODULE 5: DATA PASSING TECHNIQUES
ViewData vs ViewBag TempData ViewModel Pattern
MODULE 6: ROUTING
Conventional vs Attribute Routing Custom Constraints
MODULE 7: VIEWS & UI
Razor View Engine Layouts & Sections View Components
MODULE 8: ACTION RESULTS
ViewResult JsonResult RedirectResult
MODULE 9: HTML HELPERS
Form Helpers Custom HTML Helpers
MODULE 10: TAG HELPERS
Built-in Tag Helpers Custom Tag Helpers
MODULE 11: MODEL BINDING
FromQuery vs FromRoute Complex Binding
MODULE 12: VALIDATION
Data Annotations Remote Validation Fluent Validation
MODULE 13: STATE MANAGEMENT
Cookies & Sessions TempData
MODULE 14: FILTERS & SECURITY
Action Filters Authorize Filters Anti-forgery
MODULE 15: ENTITY FRAMEWORK CORE (DEEP DIVE)
DbContext Migrations LINQ Relationships
MODULE 16: DESIGN PATTERNS
Repository Pattern Unit of Work Clean Architecture
MODULE 17: FILE HANDLING
File Upload/Download PDF/Excel Generation
MODULE 18: ADVANCED ASP.NET CORE
Request Lifecycle Bundling & Minification Deployment
MODULE 19: PERFORMANCE & BEST PRACTICES
Caching Strategies Async Programming Secure Coding
MODULE 20: RAZOR PAGES (BONUS)
Razor Pages vs MVC
MODULE 21: REAL-WORLD PROJECTS (πŸ”₯ MUST DO)
E-Commerce Web Application Employee Management System