100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

The MVC Pattern in ASP.NET Core

Understand how ASP.NET Core implements Model-View-Controller, separating request handling, business/data concerns, and presentation into distinct, testable layers.

Razor Pages & MVCIntermediate10 min readJul 10, 2026
Analogies

The Three Roles: Model, View, Controller

ASP.NET Core MVC divides responsibility into three collaborating pieces: the Model represents application data and business rules (often POCOs, EF Core entities, or dedicated view models); the View, typically a .cshtml Razor file, is responsible purely for rendering HTML from data it is handed; and the Controller, a class deriving from Controller or ControllerBase, receives the incoming request, coordinates with services or repositories to gather or mutate data, and selects which view to render or what result to return. This separation means a change to how data is displayed rarely forces a change to how it's fetched or validated, and vice versa.

🏏

Cricket analogy: This is like a cricket broadcast where the on-field umpire (Controller) makes the calls, the statistician (Model) tracks runs and wickets, and the graphics team (View) renders the scoreboard overlay — three separate roles combining into one coherent broadcast without any single person doing all three jobs.

Actions, Routing, and Action Results

A controller's public methods are called actions, and each action typically returns an IActionResult (or a strongly-typed ActionResult<T>) such as View(), RedirectToAction(), Json(), or NotFound(), letting one action decide dynamically what kind of response to produce. Routing to a controller action is configured either via attribute routing, using [Route], [HttpGet], [HttpPost] and similar attributes directly on the action, or via conventional routing registered once in Program.cs with app.MapControllerRoute, which maps URL segments like {controller}/{action}/{id?} to matching class and method names.

🏏

Cricket analogy: This is like a bowler at the top of their mark choosing between a yorker, a bouncer, or a slower ball depending on the batter and match situation — the action (delivery) taken varies, similar to an action method returning View(), Json(), or NotFound() based on conditions.

csharp
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;

    public OrdersController(IOrderService orderService) => _orderService = orderService;

    [HttpGet("{id:int}")]
    public async Task<ActionResult<OrderDto>> GetById(int id)
    {
        var order = await _orderService.FindAsync(id);
        if (order is null) return NotFound();
        return Ok(order);
    }

    [HttpPost]
    public async Task<ActionResult<OrderDto>> Create(CreateOrderRequest request)
    {
        if (!ModelState.IsValid) return BadRequest(ModelState);

        var created = await _orderService.CreateAsync(request);
        return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
    }
}

View Models and Data Flow

Rather than passing EF Core entities straight to a view, well-structured MVC apps use dedicated view model classes that shape exactly the data a view needs, decoupling the persistence schema from presentation and avoiding accidental over-posting or leaking internal fields to the client. Controllers pass this view model to View(viewModel), and inside the .cshtml file the strongly-typed @model directive gives IntelliSense and compile-time checking against that exact shape, while TempData and ViewData remain available for transient, loosely-typed data like a one-time success message after a redirect.

🏏

Cricket analogy: This is like a scorecard summary handed to TV commentators that shows only runs, overs, and wickets — not every internal database field the scoring system tracks — similar to a view model exposing only what the view needs.

Controllers deriving from ControllerBase (used with [ApiController]) omit view-rendering helpers like View() since they're meant for APIs returning JSON, while controllers deriving from Controller include both API-style results and view-rendering support for traditional server-rendered HTML pages.

Avoid binding request input directly to your EF Core entity classes in action parameters for state-changing endpoints — an attacker could set unexpected properties (over-posting) that happen to exist on the entity. Use a dedicated request/DTO class per action and map explicitly to the entity instead.

  • MVC separates concerns: Model (data/business rules), View (presentation), Controller (request coordination).
  • Actions are public controller methods that return IActionResult or ActionResult<T>.
  • Routing can be attribute-based ([Route], [HttpGet]) or conventional via app.MapControllerRoute.
  • ControllerBase suits APIs; Controller adds view-rendering support for HTML pages.
  • Dedicated view models decouple presentation shape from persistence entities.
  • TempData and ViewData carry transient, loosely-typed data such as post-redirect success messages.
  • Use request DTOs rather than binding directly to entities to avoid over-posting vulnerabilities.

Practice what you learned

Was this page helpful?

Topics covered

#ASPNETCoreStudyNotes#MicrosoftTechnologies#TheMVCPatternInASPNETCore#MVC#Pattern#ASP#NET#StudyNotes#SkillVeris#ExamPrep