ViewBag, ViewData, and TempData
MVC gives you three “loose” ways to send data to a view. They look alike but live for different lengths of time — mixing them up is a classic source of disappearing messages.
On this page · 6 min · Intermediate
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Intermediate
- Updated
- Oct 29, 2026
Related tutorials
Learning objectives#
- Use ViewData and ViewBag and know they share storage
- Show a one-time message with TempData after a redirect
- Read TempData without removing it using Peek and Keep
- Decide when to use a view model instead
Explanation#
ViewData is a dictionary keyed by strings. ViewBag is a dynamic wrapper over the same dictionary, so ViewBag.Title and ViewData["Title"] are one value. Both live for a single request: from the action to the view it renders.
TempData survives into the next request. In MVC 5 it is stored in session by default. Reading a value marks it for deletion at the end of the request, which makes it perfect for “Saved successfully” messages after Post/Redirect/Get.
TempData.Peek("key") reads a value without marking it, and TempData.Keep("key") un-marks a value you already read, so it survives one more request.
None of the three are type-checked: a typo in a key just returns null. For the main data of a page, use a strongly typed model instead.
Syntax#
ViewBag.Title = "Products"; // same storage as ViewData["Title"]
ViewData["Count"] = 3;
TempData["Message"] = "Saved!"; // survives one redirect
return RedirectToAction("Index");
Code example: A flash message that appears exactly once#
// Controllers/ProductsController.cs
using System.Collections.Generic;
using System.Web.Mvc;
namespace ShopSite.Controllers
{
public class ProductsController : Controller
{
private static readonly List<string> Products = new List<string> { "Gel Pen", "Notebook" };
public ActionResult Index()
{
ViewBag.Title = "Products";
ViewData["Count"] = Products.Count;
return View(Products);
}
public ActionResult Create() => View();
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Create(string name)
{
Products.Add(name);
ViewBag.Lost = "ViewBag does not survive a redirect";
TempData["Message"] = quot;Product '{name}' created.";
return RedirectToAction("Index");
}
}
}
@* Views/Products/Index.cshtml *@
@model List<string>
<h1>@ViewBag.Title (@ViewData["Count"])</h1>
@if (TempData["Message"] != null)
{
<div class="alert">@TempData["Message"]</div>
}
<p>ViewBag.Lost: @(ViewBag.Lost ?? "null")</p>
<ul>
@foreach (var p in Model)
{
<li>@p</li>
}
</ul>
@* Views/Products/Create.cshtml *@
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<input name="name" />
<button>Save</button>
}
GET /Products/Create
200 OK
POST /Products/Create name=Ruler
302 Found → /Products
GET /Products
200 OK
<h1>Products (3)</h1>
<div class="alert">Product 'Ruler' created.</div>
<p>ViewBag.Lost: null</p>
<ul>
<li>Gel Pen</li>
<li>Notebook</li>
<li>Ruler</li>
</ul>
GET /Products (refresh)
200 OK
<h1>Products (3)</h1>
<p>ViewBag.Lost: null</p>
<ul>
<li>Gel Pen</li>
<li>Notebook</li>
<li>Ruler</li>
</ul>This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- The POST adds the product, sets ViewBag.Lost and TempData["Message"], then redirects to Index with a 302.
- The redirect is a brand-new request, so ViewBag is empty again — the view prints null for it.
- TempData still holds the message (it was saved in session), so Index shows it and reading it marks it for deletion.
- ViewBag.Title and ViewData["Count"] are set in Index and read in the same request.
- On refresh, the message is gone: TempData values live until they are read, then for the rest of that request only.
Real-world use case#
An HR system shows “Leave request submitted” after the form posts and redirects to the dashboard. If the user refreshes, the message disappears instead of appearing again — TempData plus Post/Redirect/Get.
Common mistakes#
A redirect is a new request, so ViewBag and ViewData start empty. Use TempData for data that must cross a redirect.
ViewData["Mesage"] silently returns null. Use constants for keys or, better, a view model.
TempData lives in session in MVC 5, so large objects use server memory for every user (and must be serialisable with out-of-process session). Store an id instead.
Best practices#
- Use a view model for the page’s main data
- Use TempData only for one-off messages across redirects
- Pick ViewBag or ViewData and use it consistently
- Use TempData.Peek when you must read without consuming
Remember this#
ViewBag = ViewData, one request.
TempData = until read, survives a redirect.
Main page data → strongly typed model.
Practice exercise#
Your task: After deleting an item, show “Product deleted” on the list page exactly once. In the view, read it first with TempData.Peek and then normally, and check it is gone after a refresh.
Need a hint?
Show solution
// Controllers/ItemsController.cs
using System.Web.Mvc;
namespace ShopSite.Controllers
{
public class ItemsController : Controller
{
public ActionResult Index() => View();
[HttpPost]
public ActionResult Delete(int id)
{
// ... delete the item ...
TempData["Message"] = "Product deleted";
return RedirectToAction("Index");
}
}
}
@* Views/Items/Index.cshtml *@
<p>Peek: @TempData.Peek("Message")</p>
<p>Read: @TempData["Message"]</p>
POST /Items/Delete/4 302 Found → /Items GET /Items 200 OK <p>Peek: Product deleted</p> <p>Read: Product deleted</p> GET /Items (refresh) 200 OK <p>Peek: </p> <p>Read: </p>
Mini quiz#
Which one survives a RedirectToAction?
Want more? Practise all ASP.NET MVC MCQs · ASP.NET MVC interview questions
My notes#
Saved in this browser.