ASP.NET MVC · Controllers & Views

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.

Intermediate 6 min Lesson 12 of 37Covers ASP.NET MVC 5.2 on .NET Framework 4.8.
Before you start:ControllersViews
0/37 in ASP.NET MVC
On this page · 6 min · Intermediate

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#

C# Illustrative
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#

C#ProductsController.cs Illustrative
// 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> }
Expected output
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 &#39;Ruler&#39; 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#

  1. The POST adds the product, sets ViewBag.Lost and TempData["Message"], then redirects to Index with a 302.
  2. The redirect is a brand-new request, so ViewBag is empty again — the view prints null for it.
  3. TempData still holds the message (it was saved in session), so Index shows it and reading it marks it for deletion.
  4. ViewBag.Title and ViewData["Count"] are set in Index and read in the same request.
  5. 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#

Expecting ViewBag to survive a redirect

A redirect is a new request, so ViewBag and ViewData start empty. Use TempData for data that must cross a redirect.

Typos in keys

ViewData["Mesage"] silently returns null. Use constants for keys or, better, a view model.

Big objects in TempData

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?
Peek does not mark the value for deletion; the normal read does, so it disappears on the next request.
Show solution
C# Illustrative
// 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>
Expected output
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#

ViewBag, ViewData, and TempData quizMultiple choice Question 1 of 3

Which one survives a RedirectToAction?

Score so far: 0/0

Want more? Practise all ASP.NET MVC MCQs · ASP.NET MVC interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.