Controllers for Views
🎯 What You'll Learn
- Action methods
- Action results
- Passing data to views
- ViewData and ViewBag
- TempData
- Model binding
Controller Basics
MVC controllers inherit from Controller class and return views.
public class ProductsController : Controller
{
// Action method
public IActionResult Index()
{
return View();
}
}
Action Results
Action methods return IActionResult or specific result types.
ViewResult
public IActionResult Index()
{
return View(); // Returns Views/Products/Index.cshtml
}
public IActionResult List()
{
return View("Index"); // Returns specific view
}
RedirectResult
public IActionResult Create()
{
// Redirect to Index action
return RedirectToAction("Index");
}
public IActionResult Save()
{
// Redirect to different controller
return RedirectToAction("Index", "Home");
}
public IActionResult External()
{
// Redirect to URL
return Redirect("https://example.com");
}
Other Action Results
// Not Found (404)
public IActionResult Details(int id)
{
var product = _context.Products.Find(id);
if (product == null)
return NotFound();
return View(product);
}
// Unauthorized (401)
public IActionResult Admin()
{
return Unauthorized();
}
// Forbidden (403)
public IActionResult Secret()
{
return Forbid();
}
// Content (plain text)
public IActionResult Text()
{
return Content("Hello, World!");
}
// JSON
public IActionResult Data()
{
return Json(new { message = "Hello" });
}
Passing Data to Views
Strongly-Typed Models (Recommended)
public async Task<IActionResult> Index()
{
var products = await _context.Products.ToListAsync();
return View(products); // Pass model
}
@model List<Product>
<h1>Products</h1>
@foreach (var product in Model)
{
<p>@product.Name</p>
}
ViewData
public IActionResult Index()
{
ViewData["Title"] = "Products";
ViewData["Count"] = 10;
return View();
}
<h1>@ViewData["Title"]</h1>
<p>Total: @ViewData["Count"]</p>
ViewBag
public IActionResult Index()
{
ViewBag.Title = "Products";
ViewBag.Count = 10;
return View();
}
<h1>@ViewBag.Title</h1>
<p>Total: @ViewBag.Count</p>
ViewData: Dictionary, requires casting
ViewBag: Dynamic, no casting needed
Recommendation: Use strongly-typed models instead!
TempData
TempData persists data across redirects (one request only).
public IActionResult Create(Product product)
{
_context.Products.Add(product);
_context.SaveChanges();
TempData["Message"] = "Product created successfully!";
return RedirectToAction("Index");
}
public IActionResult Index()
{
// TempData available after redirect
return View();
}
@if (TempData["Message"] != null)
{
<div class="alert alert-success">
@TempData["Message"]
</div>
}
Model Binding
Model binding automatically maps request data to action parameters.
Route Parameters
// URL: /Products/Details/5
public IActionResult Details(int id)
{
var product = _context.Products.Find(id); // id = 5
return View(product);
}
Query String
// URL: /Products?page=2&pageSize=20
public IActionResult Index(int page = 1, int pageSize = 10)
{
var products = _context.Products
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
return View(products);
}
Form Data
// GET: Show form
public IActionResult Create()
{
return View();
}
// POST: Handle form submission
[HttpPost]
public IActionResult Create(Product product)
{
if (ModelState.IsValid)
{
_context.Products.Add(product);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(product);
}
Binding Attributes
public IActionResult Search(
[FromQuery] string query,
[FromRoute] int id,
[FromForm] Product product,
[FromHeader] string userAgent)
{
return View();
}
Complete InvenTrack Example
public class ProductsController : Controller
{
private readonly InvenTrackDbContext _context;
public ProductsController(InvenTrackDbContext context)
{
_context = context;
}
// GET: /Products
public async Task<IActionResult> Index(string? search)
{
var query = _context.Products.AsQueryable();
if (!string.IsNullOrEmpty(search))
{
query = query.Where(p => p.Name.Contains(search));
ViewBag.SearchTerm = search;
}
var products = await query.ToListAsync();
return View(products);
}
// GET: /Products/Details/5
public async Task<IActionResult> Details(int id)
{
var product = await _context.Products.FindAsync(id);
if (product == null)
return NotFound();
return View(product);
}
// GET: /Products/Create
public IActionResult Create()
{
return View();
}
// POST: /Products/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Product product)
{
if (ModelState.IsValid)
{
_context.Products.Add(product);
await _context.SaveChangesAsync();
TempData["Success"] = $"Product '{product.Name}' created successfully!";
return RedirectToAction("Index");
}
return View(product);
}
// GET: /Products/Edit/5
public async Task<IActionResult> Edit(int id)
{
var product = await _context.Products.FindAsync(id);
if (product == null)
return NotFound();
return View(product);
}
// POST: /Products/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, Product product)
{
if (id != product.Id)
return NotFound();
if (ModelState.IsValid)
{
_context.Update(product);
await _context.SaveChangesAsync();
TempData["Success"] = "Product updated successfully!";
return RedirectToAction("Index");
}
return View(product);
}
// POST: /Products/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
{
var product = await _context.Products.FindAsync(id);
if (product != null)
{
_context.Products.Remove(product);
await _context.SaveChangesAsync();
TempData["Success"] = "Product deleted successfully!";
}
return RedirectToAction("Index");
}
}
Best Practices
- Strongly-typed models: Prefer over ViewData/ViewBag
- Async actions: Use async/await for I/O operations
- PRG pattern: Post-Redirect-Get to prevent duplicate submissions
- TempData: Use for success/error messages after redirects
- Model validation: Check ModelState.IsValid
- Anti-forgery tokens: Use [ValidateAntiForgeryToken]
- Return specific results: NotFound(), Redirect(), etc.
Key Takeaways
- Controller: Base class for MVC controllers
- IActionResult: Return type for action methods
- View(): Returns a view with optional model
- RedirectToAction(): Redirect to another action
- Model binding: Automatic parameter mapping
- ViewData/ViewBag: Pass additional data to views
- TempData: Persist data across redirects
- ModelState: Validation state
You now understand controllers for views! In the next section, we'll explore Razor Syntax—how to write dynamic HTML with C# code.