Section 2 of 6

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.

Basic Controller C#
public class ProductsController : Controller
{
    // Action method
    public IActionResult Index()
    {
        return View();
    }
}

Action Results

Action methods return IActionResult or specific result types.

ViewResult

Return a View C#
public IActionResult Index()
{
    return View(); // Returns Views/Products/Index.cshtml
}

public IActionResult List()
{
    return View("Index"); // Returns specific view
}

RedirectResult

Redirect to Another Action C#
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

Common Action Results C#
// 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)

Pass Model to View C#
public async Task<IActionResult> Index()
{
    var products = await _context.Products.ToListAsync();
    return View(products); // Pass model
}
Views/Products/Index.cshtml HTML
@model List<Product>

<h1>Products</h1>

@foreach (var product in Model)
{
    <p>@product.Name</p>
}

ViewData

Dictionary-Based Data C#
public IActionResult Index()
{
    ViewData["Title"] = "Products";
    ViewData["Count"] = 10;
    return View();
}
Access in View HTML
<h1>@ViewData["Title"]</h1>
<p>Total: @ViewData["Count"]</p>

ViewBag

Dynamic Properties C#
public IActionResult Index()
{
    ViewBag.Title = "Products";
    ViewBag.Count = 10;
    return View();
}
Access in View HTML
<h1>@ViewBag.Title</h1>
<p>Total: @ViewBag.Count</p>
⚠️ ViewData vs ViewBag

ViewData: Dictionary, requires casting
ViewBag: Dynamic, no casting needed
Recommendation: Use strongly-typed models instead!

TempData

TempData persists data across redirects (one request only).

Store Message for Next Request C#
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();
}
Display in View HTML
@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

Bind from URL C#
// URL: /Products/Details/5
public IActionResult Details(int id)
{
    var product = _context.Products.Find(id); // id = 5
    return View(product);
}

Query String

Bind from Query C#
// 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

Bind from Form C#
// 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

Explicit Binding Sources C#
public IActionResult Search(
    [FromQuery] string query,
    [FromRoute] int id,
    [FromForm] Product product,
    [FromHeader] string userAgent)
{
    return View();
}

Complete InvenTrack Example

Controllers/ProductsController.cs C#
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
🎯 Next Steps

You now understand controllers for views! In the next section, we'll explore Razor Syntax—how to write dynamic HTML with C# code.