DEV Community

Sumeet Dugg
Sumeet Dugg

Posted on

ASP.NET Core MVC Explained — Step-by-Step with Real Code & Best Practices

Understand models, views, controllers and why you should use MVC for scalable, testable applications.


Introduction

Model-View-Controller (MVC) is the standard architectural pattern for building modern web applications in ASP.NET Core. It is a design pattern that separates an application into three main logical components: the Model, the View, and the Controller.

This separation helps manage complexity when building applications, allowing you to focus on one aspect of the implementation at a time.

During my college days, I built a Banking System project as part of my academic curriculum. You can check it out here: bankingProject-c-.net
Challenges & Limitations I Faced Without MVC

Building the project without MVC introduced several problems:

  • No Separation of Concerns — All HTML, business logic, and data handling lived together. How MVC Solves These Problems In contrast, MVC provides:
  • Models for data and business logic
  • Views for UI
  • Controllers to handle user interaction and route requests

This separation makes the code easier to maintain, easier to test, and cleaner and scalable.


1. The Core Components

Each component has a distinct responsibility. Understanding the boundaries between them is key to writing clean code.

M — The Model

The Model represents the shape of your data and the business logic. It is the "truth" of your application.

  • Responsibilities: Manages the state of the application and handles data logic (e.g., interacting with the database, validating data rules).
  • In ASP.NET Core: These are typically plain C# classes (POCOs).
  • Domain Models: Represent database tables (e.g. Product, User)
  • View Models: Data specifically shaped for display on a screen (e.g. ProductSummaryViewModel that combines data from multiple tables).

V — The View

The View is the User Interface (UI). Its only job is to display information to the user.

  • Responsibilities: Renders the user interface elements (HTML, CSS) using data provided by the Controller. Ideally, a View should not contain complex logic — only presentation logic (e.g., loops to display a list, if-statements to show/hide buttons).
  • In ASP.NET Core: These are .cshtml files using the Razor view engine. Razor allows you to embed C# code directly into HTML to generate dynamic content.

C — The Controller

The Controller is the coordinator. It handles user input and interaction.

  • Responsibilities: Receives the user's request, figures out what to do with it (usually by calling services or the Model), and then selects which View to return to the user.
  • In ASP.NET Core: Controllers are C# classes that inherit from Controller. They contain public methods called Actions (e.g., Index(), Details(id)).

2. The Request Lifecycle

To understand MVC, you must follow the path of a web request (e.g., a user clicking a link).

  1. Routing: A user navigates to a URL like Books/Details/2. The ASP.NET Core Routing engine determines it needs the BooksController and the Details action method.
  2. Controller Action: The Details method in the Controller executes.
  3. Model Retrieval: The Model retrieves the data (perhaps from a SQL database).
  4. View Selection: The Controller receives the data, decides which View to use (usually Details.cshtml), and passes the data (the Model) to it.
  5. Rendering: The Razor View Engine takes the HTML template and merges it with the Model data to produce a standard HTML page.
  6. Response: The final HTML is sent back to the user's browser.

3. Key Advantages of MVC Architecture

  • Separation of Concerns (SoC): By decoupling the UI (View), Data (Model), and Application Logic (Controller), the application becomes easier to manage. Front-end developers can modify HTML/CSS without breaking backend logic, and vice versa.
  • Enhanced Testability: Since Controllers and Models are standard C# classes independent of the UI, you can easily write unit tests for them without simulating a browser.
  • Full Control Over HTML: Unlike ASP.NET Web Forms which auto-generated complex HTML, MVC gives you absolute control over rendered markup — critical for modern CSS frameworks like Bootstrap or Tailwind.
  • Asynchronous Support: ASP.NET Core MVC is built to handle async/await efficiently, allowing applications to handle more concurrent requests without blocking the server.

4. MVC Project Structure in ASP.NET Core

When you create a new MVC project via Visual Studio or CLI (dotnet new mvc), you get this standard folder structure:

Note: If you want to learn how to download and install Visual Studio, check out this guide: Getting Started with C# Programming


Create Your First ASP.NET Core MVC Project

Step 1 — Open Visual Studio and Create a New Project

  1. Open Visual Studio
  2. Click Create a new project

Step 2 — Select Project Template

  • Choose ASP.NET Core Web App (Model-View-Controller)
  • Language: C#
  • Platform: Windows / .NET
  • Click Next

Step 3 — Configure the Project

Fill in the following details:

  • Project name: BooksLibrary
  • Location: Default (or your preferred path)
  • Solution name: BooksLibrary

Click Next.

Note: You can choose your desired location to store project files. I recommend keep default location as it is now.

Step 4 — Choose .NET Version

  • Select the latest available version: .NET 9 / .NET 10 (LTS)
  • Set Authentication type to None
  • Click Create

Visual Studio will scaffold the project and open the default MVC structure.


Understanding MVC Through Code Examples

M — The Model

In MVC architecture, the Model carries the heaviest responsibility. It represents real-world entities, business logic, and domain rules that form the foundation of your application.

For the BooksLibrary project, we need one class — Book.cs — to hold core information about each library book.

Right-click the Models folder in Solution Explorer → AddClass → name it Book.cs → click Add.

namespace BooksLibrary.Models
{
    public class Book
    {
        public int Id { get; set; }
        public string? Title { get; set; }
        public string? Author { get; set; }
        public string? Genre { get; set; }
        public decimal Price { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: A namespace is a container that groups related classes in C#. It prevents naming conflicts and keeps your code maintainable.


V — The View

Views are the visual layer of your MVC application. They receive data from Controllers and render it as HTML. Views should not contain business logic — only presentation logic.

Views use Razor syntax (.cshtml files) that blends HTML with C# code.

Folder Structure for Views

Views/
  ├── Books/
  │   ├── Index.cshtml
  │   └── Details.cshtml
  ├── Shared/
  │   └── _Layout.cshtml
  └── _ViewStart.cshtml
Enter fullscreen mode Exit fullscreen mode

Index.cshtml — Book List View

Right-click Views/BooksAdd View → name it Index.cshtml.

@model IEnumerable<BooksLibrary.Models.Book>
@{
    ViewData["Title"] = "Books Library";
}

<div class="container mt-4">
    <h2>Our Book Collections</h2>
    <table class="table table-striped table-hover">
        <thead>
            <tr>
                <th>Title</th>
                <th>Author</th>
                <th>Genre</th>
                <th>Price</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var book in Model)
            {
                <tr>
                    <td>@book.Title</td>
                    <td>@book.Author</td>
                    <td>@book.Genre</td>
                    <td>$@book.Price.ToString("F2")</td>
                    <td>
                        <a asp-action="Details" asp-route-id="@book.Id"
                           class="btn btn-sm btn-info">Details</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
</div>
Enter fullscreen mode Exit fullscreen mode

Breaking Down the Razor Syntax

The @model Directive — tells the view what type of data to expect. Must be the first line.

@model IEnumerable<BooksLibrary.Models.Book>
Enter fullscreen mode Exit fullscreen mode

Setting View Metadata@{ } blocks let you write C# code. ViewData passes info like the page title to the layout.

@{
    ViewData["Title"] = "Books Library";
}
Enter fullscreen mode Exit fullscreen mode

Outputting Variables — the @ symbol switches from HTML to C#.

@book.Title
Enter fullscreen mode Exit fullscreen mode

C# Control Structures — use foreach, if, while inside Razor views.

@foreach (var book in Model)
{
    <tr>
        <td>@book.Title</td>
    </tr>
}
Enter fullscreen mode Exit fullscreen mode

Tag Helpersasp-action generates the correct route URL; asp-route-id passes the book ID as a parameter.

<a asp-action="Details" asp-route-id="@book.Id" class="btn btn-sm btn-info">Details</a>
Enter fullscreen mode Exit fullscreen mode

Details.cshtml — Single Book View

Create Details.cshtml in the Views/Books folder:

@model BooksLibrary.Models.Book
@{
    ViewData["Title"] = "Book Details";
}

<div class="container mt-4">
    <h2>Book Details</h2>
    <div class="card">
        <div class="card-body">
            <h4 class="card-title">@Model.Title</h4>
            <dl class="row">
                <dt class="col-sm-3">Author</dt>
                <dd class="col-sm-9">@Model.Author</dd>
                <dt class="col-sm-3">Genre</dt>
                <dd class="col-sm-9">@Model.Genre</dd>
                <dt class="col-sm-3">Price</dt>
                <dd class="col-sm-9">$@Model.Price.ToString("F2")</dd>
            </dl>
            <div class="mt-3">
                <a asp-action="Index" class="btn btn-secondary">Back to List</a>
            </div>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

This view expects a single Book object rather than a collection — note the @model directive reflects that.


C — The Controller

Controllers are the traffic cops of your MVC application. They handle incoming requests, coordinate between Models and Views, and decide what response to send back.

When a user clicks a link or submits a form, the Controller:

  1. Processes the incoming request
  2. Interacts with the Model (retrieves or updates data)
  3. Selects the appropriate View
  4. Passes data to that View
  5. Returns the rendered result to the user

Creating the Books Controller

Right-click ControllersAdd Controller → choose MVC Controller - Empty → name it BooksController.cs.

using System.Diagnostics;
using BooksLibrary.Models;
using Microsoft.AspNetCore.Mvc;

namespace BooksLibrary.Controllers
{
    public class BooksController : Controller
    {
        private static List<Book> _books = new List<Book>
        {
            new Book
            {
                Id = 1,
                Title = "The Alchemist",
                Author = "Paulo Coelho",
                Genre = "Fantasy/Adventure",
                Price = 12.99m
            },
            new Book
            {
                Id = 2,
                Title = "Harry Potter and the Philosopher's Stone",
                Author = "J.K. Rowling",
                Genre = "Fantasy",
                Price = 14.99m
            },
            new Book
            {
                Id = 3,
                Title = "1984",
                Author = "George Orwell",
                Genre = "Dystopian",
                Price = 13.99m
            }
        };

        public IActionResult Index()
        {
            return View(_books);
        }

        public IActionResult Details(int id)
        {
            var book = _books.FirstOrDefault(b => b.Id == id);

            if (book == null)
            {
                return NotFound();
            }

            return View(book);
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel
            {
                RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Breaking Down the Controller

Controller Inheritance

public class BooksController : Controller
Enter fullscreen mode Exit fullscreen mode

Every controller inherits from the Controller base class, which provides View(), RedirectToAction(), and NotFound().

Action Methods

public IActionResult Index()
{
    return View(_books);
}
Enter fullscreen mode Exit fullscreen mode

Action methods return IActionResult — typically a view, redirect, or HTTP status code. View(_books) passes the book collection to the Index view.

Route Parameters

public IActionResult Details(int id)
Enter fullscreen mode Exit fullscreen mode

Parameters are automatically populated from the URL. For /Books/Details/3, the id parameter receives the value 3.


Running the Application

  1. In Visual Studio, find the green ▶ Play button in the top toolbar (labelled "IIS Express" or your project name)
  2. Click it or press F5

The browser will open showing your books list. Click the Details button on any book to see its individual details page.


What We Accomplished

We built a complete MVC application from the ground up, covering all three core components:

  • The Model — The Book class defines the data structure with properties like Title, Author, Genre, and Price. It is the foundation everything else is built on.
  • The Views — Two Razor views handle the presentation layer: Index.cshtml lists all books in a table, and Details.cshtml displays information for a single book.
  • The ControllerBooksController orchestrates everything. It handles incoming requests, manages the book collection, and coordinates between the Model and Views to deliver the right content.

This is the essence of MVC — clean separation of concerns that makes your application easier to maintain, test, and scale.


Found This Helpful?

Leave a comment below sharing what you learned, what challenges you faced, or what you're planning to build next!

Related links:

Top comments (0)