The MVC Pattern — Model, View and Controller Roles

The Model-View-Controller (MVC) pattern divides an application into three interconnected roles. The Model represents the data and business rules. The View renders the visual presentation (HTML for web applications). The Controller handles user input (HTTP requests), retrieves or updates the model, and selects the view to render. This separation of concerns makes each part independently testable, independently modifiable, and clear in its responsibility. ASP.NET Core MVC is Microsoft’s production-grade implementation of this pattern for server-rendered web applications.

The Three Roles

// ── MODEL — data and business logic ──────────────────────────────────────
// Models are plain C# classes. In MVC they appear in two forms:
// 1. Domain/entity models (what the database stores)
// 2. View models (shaped specifically for what a view needs to render)

// View model — shaped for a specific view
public class PostIndexViewModel
{
    public IReadOnlyList<PostSummary> Posts   { get; init; } = [];
    public int                       Page     { get; init; }
    public int                       PageSize { get; init; }
    public int                       Total    { get; init; }
    public bool                      HasNext  => (Page * PageSize) < Total;
    public bool                      HasPrev  => Page > 1;
}

public record PostSummary(int Id, string Title, string Slug, DateTime PublishedAt);

// ── CONTROLLER — handles requests, coordinates model and view ─────────────
public class PostsController : Controller   // note: Controller, not ControllerBase
{
    private readonly IPostService _service;

    public PostsController(IPostService service) => _service = service;

    // GET /posts  (or /posts/index by conventional routing)
    public async Task<IActionResult> Index(int page = 1)
    {
        // 1. Retrieve model data via service
        var posts = await _service.GetPublishedAsync(page, pageSize: 10);
        var total = await _service.CountPublishedAsync();

        // 2. Build view model
        var vm = new PostIndexViewModel
        {
            Posts    = posts.Select(p => new PostSummary(p.Id, p.Title, p.Slug, p.PublishedAt.Value)).ToList(),
            Page     = page,
            PageSize = 10,
            Total    = total,
        };

        // 3. Return ViewResult — Razor renders Views/Posts/Index.cshtml
        return View(vm);
    }
}

// ── VIEW — renders HTML (Views/Posts/Index.cshtml) ────────────────────────
// @model PostIndexViewModel        ← declares what type this view expects
//
// <h1>Published Posts</h1>
// @foreach (var post in Model.Posts)
// {
//     <article>
//         <h2><a href="/posts/@post.Slug">@post.Title</a></h2>
//         <time>@post.PublishedAt.ToString("MMMM d, yyyy")</time>
//     </article>
// }
Note: In ASP.NET Core MVC, controllers inherit from Controller (not ControllerBase as in Web API). Controller adds view-related methods: View(), PartialView(), RedirectToAction(), TempData, and ViewBag. ControllerBase is the minimal base for API controllers that return JSON. Never inherit from Controller in a Web API controller — it adds unnecessary overhead. Never inherit from ControllerBase in an MVC controller — you lose access to view-related helpers.
Tip: Always use View Models rather than passing domain entities directly to views. A view model is shaped specifically for what the view needs — it avoids exposing sensitive entity properties (password hashes, audit fields), prevents over-posting vulnerabilities (where a form submission sets a field not intended for user input), and makes it explicit what data a view depends on. The extra mapping code is worth the clarity, security, and testability.
Warning: Do not put business logic in controllers or views. Controllers should be thin: validate input, call service methods, build view models, return views. Views should only render data — no calculations, no database calls, no business rules. Business logic belongs in domain entities and application services. A controller action that is more than 15–20 lines is usually doing too much and should be refactored.

MVC vs Web API — When to Choose Each

Concern ASP.NET Core MVC ASP.NET Core Web API
Output format HTML (Razor views) JSON (or XML)
Client Browser (server-rendered) Angular, mobile, other services
State Session, cookies, ViewBag Stateless, JWT tokens
Best for Admin panels, CMS, marketing sites REST APIs, SPAs, microservices
Base class Controller ControllerBase
Registration AddControllersWithViews AddControllers

Common Mistakes

Mistake 1 — Inheriting from ControllerBase in an MVC controller

❌ Wrong — View(), TempData, and Redirect helpers are unavailable:

public class HomeController : ControllerBase  // wrong base for MVC!

✅ Correct — inherit from Controller for MVC controllers.

Mistake 2 — Passing entity models directly to views (over-posting vulnerability)

❌ Wrong — view accesses all entity properties including sensitive ones; form posts can set any property.

✅ Correct — always create a dedicated ViewModel for each view.

🧠 Test Yourself

In the MVC pattern, which component is responsible for deciding which view to render and what data to pass to it?