ASP.NET Core MVC Basics Cheat Sheet

Introduction to ASP.NET Core MVC

ASP.NET Core MVC is a rich, open-source framework for building web applications and APIs using the Model-View-Controller architectural pattern. The MVC pattern separates an application into three main components, promoting a clear separation of concerns that makes applications more maintainable and testable.

MVC Architecture Overview

Model

  • Represents the application’s data and business logic
  • Responsible for data validation and enforcing business rules
  • Typically interacts with data storage (databases)
  • Independent of the UI
  • Examples: Entity classes, data access code, business services

View

  • Handles the UI presentation layer
  • Renders the model data for display
  • Uses Razor syntax (.cshtml files)
  • Should contain minimal logic
  • Stored in the /Views folder by default

Controller

  • Handles user interactions and input
  • Processes incoming requests
  • Works with model data
  • Selects appropriate views to render
  • Stored in the /Controllers folder by default
  • Entry point for handling HTTP requests

Project Structure

/YourProject
├── /Controllers        # Controller classes
├── /Models             # Model classes and data context
├── /Views              # View templates
│   ├── /Home           # Views for HomeController
│   ├── /Shared         # Shared views (layouts, partials)
│   └── _ViewImports.cshtml # Import directives for views
│   └── _ViewStart.cshtml   # Common view initialization
├── /wwwroot            # Static files (CSS, JS, images)
│   ├── /css
│   ├── /js
│   └── /images
├── Program.cs          # Application entry point and startup
├── appsettings.json    # Configuration settings
└── YourProject.csproj  # Project file

Controller Basics

Creating a Controller

using Microsoft.AspNetCore.Mvc;

namespace YourApp.Controllers
{
    public class HomeController : Controller
    {
        // GET: /Home/
        public IActionResult Index()
        {
            return View();
        }
        
        // GET: /Home/About
        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";
            return View();
        }
    }
}

Controller Action Return Types

Return TypeDescription
ViewResultRenders a view to the response
PartialViewResultRenders a partial view
RedirectResultRedirects to a URL
RedirectToActionResultRedirects to another action
JsonResultReturns a JSON-formatted response
ContentResultReturns a text/plain response
FileResultReturns a file
StatusCodeResultReturns a specific HTTP status code

Action Method Helper Methods

// Return a view
return View();                   // Uses action name as view name
return View("ViewName");         // Specify view name
return View(model);              // Pass a model to the view
return View("ViewName", model);  // Specify view and model

// Redirection
return RedirectToAction("Index");
return RedirectToAction("Index", "Home");
return RedirectToAction("Index", new { id = 123 });
return Redirect("/Home/Index");
return RedirectToRoute("routename");

// API responses
return Json(new { name = "John", age = 30 });
return Content("Plain text response");
return File("/path/to/file.pdf", "application/pdf");
return NotFound();               // 404
return BadRequest();             // 400
return Unauthorized();           // 401
return StatusCode(StatusCodes.Status500InternalServerError);

Route Configuration

Convention-based Routing

// In Program.cs
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Attribute Routing

[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet]  // GET: /api/products
    public IActionResult GetAll() { ... }
    
    [HttpGet("{id}")]  // GET: /api/products/5
    public IActionResult GetById(int id) { ... }
    
    [HttpPost]  // POST: /api/products
    public IActionResult Create([FromBody] Product product) { ... }
    
    [HttpPut("{id}")]  // PUT: /api/products/5
    public IActionResult Update(int id, [FromBody] Product product) { ... }
    
    [HttpDelete("{id}")]  // DELETE: /api/products/5
    public IActionResult Delete(int id) { ... }
}

Route Constraints

// Numeric constraint
app.MapControllerRoute(
    name: "product",
    pattern: "Product/{id:int}",
    defaults: new { controller = "Product", action = "Details" });

// Common constraints:
// {id:int}      - Integer values
// {id:guid}     - GUID values
// {name:alpha}  - Alphabetic characters
// {file:regex(^[a-z0-9]+\.(jpg|png)$)} - Regular expression

Model Basics

Creating a Simple Model

public class Movie
{
    public int Id { get; set; }
    
    [Required]
    [StringLength(100)]
    public string Title { get; set; }
    
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }
    
    [Range(1, 10)]
    public decimal Rating { get; set; }
    
    [Required]
    [StringLength(500)]
    public string Description { get; set; }
}

Data Annotations

AnnotationDescription
[Required]Specifies that the property must have a value
[StringLength(max)]Specifies max length of a string
[Range(min, max)]Specifies a numeric range
[RegularExpression(pattern)]Validates against a regex pattern
[EmailAddress]Validates email format
[Phone]Validates phone number format
[Url]Validates URL format
[DataType(DataType.X)]Specifies the data type (Date, Password, etc)
[Display(Name = "X")]Sets display name for UI
[Compare("PropertyName")]Compares with another property

Database Context with Entity Framework Core

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
    
    public DbSet<Movie> Movies { get; set; }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Define relationships, constraints, etc.
        modelBuilder.Entity<Movie>()
            .HasIndex(m => m.Title);
    }
}

// In Program.cs
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

Model Binding

// Binding from route values, query strings, or form data
public IActionResult Edit(int id)

// Binding from request body (for APIs)
public IActionResult Create([FromBody] Movie movie)

// Binding from specific sources
public IActionResult Update(
    [FromRoute] int id,
    [FromBody] Movie movie,
    [FromQuery] bool validate,
    [FromHeader(Name = "Authorization")] string authorization)

View Basics

Creating a Simple View

@model YourApp.Models.Movie

@{
    ViewData["Title"] = "Movie Details";
}

<h2>@Model.Title</h2>

<dl>
    <dt>@Html.DisplayNameFor(model => model.ReleaseDate)</dt>
    <dd>@Html.DisplayFor(model => model.ReleaseDate)</dd>
    
    <dt>@Html.DisplayNameFor(model => model.Rating)</dt>
    <dd>@Html.DisplayFor(model => model.Rating)</dd>
    
    <dt>@Html.DisplayNameFor(model => model.Description)</dt>
    <dd>@Html.DisplayFor(model => model.Description)</dd>
</dl>

Razor Syntax Basics

@* This is a comment *@

@* Variables and code blocks *@
@{
    var message = "Hello, World!";
    var currentTime = DateTime.Now;
}

@* Displaying values *@
<p>@message</p>
<p>The time is: @currentTime</p>

@* Conditional rendering *@
@if (Model.Rating > 8)
{
    <p>Highly rated!</p>
}
else
{
    <p>Average rating</p>
}

@* Loops *@
<ul>
    @foreach (var item in Model.Items)
    {
        <li>@item.Name</li>
    }
</ul>

@* Switch statements *@
@switch (Model.Status)
{
    case "A":
        <p>Active</p>
        break;
    case "I":
        <p>Inactive</p>
        break;
    default:
        <p>Unknown</p>
        break;
}

Layouts and Partial Views

@* _Layout.cshtml *@
<!DOCTYPE html>
<html>
<head>
    <title>@ViewData["Title"] - My App</title>
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <partial name="_NavbarPartial" />
    </header>
    
    <div class="container">
        @RenderBody()
    </div>
    
    <footer>
        <p>&copy; @DateTime.Now.Year - My Application</p>
    </footer>
    
    @RenderSection("Scripts", required: false)
</body>
</html>

@* _NavbarPartial.cshtml *@
<nav>
    <ul>
        <li><a asp-controller="Home" asp-action="Index">Home</a></li>
        <li><a asp-controller="Movies" asp-action="Index">Movies</a></li>
    </ul>
</nav>

@* View using layout (often in _ViewStart.cshtml) *@
@{
    Layout = "_Layout";
}

Tag Helpers

@* Navigation links *@
<a asp-controller="Home" asp-action="Index">Home</a>
<a asp-controller="Movies" asp-action="Details" asp-route-id="@Model.Id">Movie Details</a>

@* Form tag helpers *@
<form asp-controller="Account" asp-action="Login" method="post">
    <div class="form-group">
        <label asp-for="Email"></label>
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </div>
    
    <div class="form-group">
        <label asp-for="Password"></label>
        <input asp-for="Password" class="form-control" />
        <span asp-validation-for="Password" class="text-danger"></span>
    </div>
    
    <button type="submit" class="btn btn-primary">Login</button>
</form>

@* Environment tag helper *@
<environment include="Development">
    <script src="~/js/site.js"></script>
</environment>
<environment exclude="Development">
    <script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>

ViewData, ViewBag, and TempData

// In Controller
public IActionResult Index()
{
    // ViewData (dictionary)
    ViewData["Title"] = "Home Page";
    ViewData["CurrentTime"] = DateTime.Now;
    
    // ViewBag (dynamic)
    ViewBag.Message = "Welcome to my app";
    ViewBag.User = new { Name = "John", Role = "Admin" };
    
    // TempData (persists for the next request)
    TempData["Notification"] = "Your changes were saved!";
    
    return View();
}
@* In View *@
<h1>@ViewData["Title"]</h1>
<p>Current time: @ViewData["CurrentTime"]</p>

<h2>@ViewBag.Message</h2>
<p>User: @ViewBag.User.Name (@ViewBag.User.Role)</p>

@if (TempData["Notification"] != null)
{
    <div class="alert alert-success">
        @TempData["Notification"]
    </div>
}

Form Handling and Validation

Form Submission and Model Binding

// GET: /Movies/Create
public IActionResult Create()
{
    return View();
}

// POST: /Movies/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Movie movie)
{
    if (ModelState.IsValid)
    {
        _context.Add(movie);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

Client-Side Validation

@* Add these to layout or view *@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

@* In a form *@
<form asp-action="Create">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    
    <div class="form-group">
        <label asp-for="Title" class="control-label"></label>
        <input asp-for="Title" class="form-control" />
        <span asp-validation-for="Title" class="text-danger"></span>
    </div>
    
    <div class="form-group">
        <label asp-for="ReleaseDate" class="control-label"></label>
        <input asp-for="ReleaseDate" class="form-control" />
        <span asp-validation-for="ReleaseDate" class="text-danger"></span>
    </div>
    
    <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" />
    </div>
</form>

Custom Validation

// Custom validation attribute
public class FutureDateAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        DateTime date = (DateTime)value;
        if (date < DateTime.Today)
        {
            return new ValidationResult("Date must be in the future!");
        }
        return ValidationResult.Success;
    }
}

// Using the custom attribute in a model
public class Event
{
    public int Id { get; set; }
    
    [Required]
    public string Name { get; set; }
    
    [FutureDate]
    [DataType(DataType.Date)]
    public DateTime EventDate { get; set; }
}

Dependency Injection

Registering Services

// In Program.cs
// Built-in service registration
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Various service lifetimes
builder.Services.AddTransient<ITransientService, TransientService>();
builder.Services.AddScoped<IScopedService, ScopedService>();
builder.Services.AddSingleton<ISingletonService, SingletonService>();

// Register a custom service
builder.Services.AddScoped<IMovieService, MovieService>();

Using Injected Services

public class MoviesController : Controller
{
    private readonly IMovieService _movieService;
    private readonly ILogger<MoviesController> _logger;
    
    // Constructor injection
    public MoviesController(
        IMovieService movieService,
        ILogger<MoviesController> logger)
    {
        _movieService = movieService;
        _logger = logger;
    }
    
    public async Task<IActionResult> Index()
    {
        _logger.LogInformation("Getting all movies");
        var movies = await _movieService.GetAllMoviesAsync();
        return View(movies);
    }
}

Configuration

Reading Configuration

// In Program.cs
var builder = WebApplication.CreateBuilder(args);
// Configuration is already set up by the builder

// Reading configuration in a controller
public class HomeController : Controller
{
    private readonly IConfiguration _configuration;
    
    public HomeController(IConfiguration configuration)
    {
        _configuration = configuration;
    }
    
    public IActionResult Index()
    {
        // Reading values
        var apiKey = _configuration["ApiKey"];
        var timeoutSeconds = _configuration.GetValue<int>("Settings:TimeoutSeconds");
        var connectionString = _configuration.GetConnectionString("DefaultConnection");
        
        return View();
    }
}

Configuration in appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDb;Trusted_Connection=True;"
  },
  "ApiKey": "your-api-key-here",
  "Settings": {
    "TimeoutSeconds": 30,
    "EnableFeature": true
  }
}

Options Pattern

// Options class
public class MovieApiOptions
{
    public string BaseUrl { get; set; }
    public string ApiKey { get; set; }
    public int TimeoutSeconds { get; set; }
}

// In Program.cs
builder.Services.Configure<MovieApiOptions>(
    builder.Configuration.GetSection("MovieApi"));
    
// In a service
public class MovieService : IMovieService
{
    private readonly MovieApiOptions _options;
    
    public MovieService(IOptions<MovieApiOptions> options)
    {
        _options = options.Value;
    }
    
    public async Task<Movie> GetMovieAsync(int id)
    {
        // Use _options.BaseUrl, _options.ApiKey, etc.
    }
}

Middleware and Error Handling

Configuring Middleware

// In Program.cs
var app = builder.Build();

// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Global Error Handling

// Error controller
public class ErrorController : Controller
{
    [Route("Error/{statusCode}")]
    public IActionResult HttpStatusCodeHandler(int statusCode)
    {
        var statusCodeResult = 
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        switch (statusCode)
        {
            case 404:
                ViewBag.ErrorMessage = "Sorry, the page you requested could not be found";
                ViewBag.Path = statusCodeResult.OriginalPath;
                break;
            case 500:
                ViewBag.ErrorMessage = "Sorry, something went wrong on the server";
                break;
        }

        return View("Error");
    }
    
    [Route("Error")]
    [AllowAnonymous]
    public IActionResult Error()
    {
        var exceptionDetails = 
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            
        ViewBag.ExceptionPath = exceptionDetails.Path;
        ViewBag.ExceptionMessage = exceptionDetails.Error.Message;
        
        return View("Error");
    }
}

Custom Middleware

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;
    
    public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation(
            "Request {Method} {Path}", 
            context.Request.Method, 
            context.Request.Path);
            
        // Call the next middleware in the pipeline
        await _next(context);
        
        _logger.LogInformation(
            "Response {StatusCode}", 
            context.Response.StatusCode);
    }
}

// Extension method for cleaner registration
public static class RequestLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder app)
    {
        return app.UseMiddleware<RequestLoggingMiddleware>();
    }
}

// In Program.cs
app.UseRequestLogging();

Areas

Setting Up Areas

// In Program.cs
app.MapControllerRoute(
    name: "areaRoute",
    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
    
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Creating Area Structure

/Areas
  /Admin
    /Controllers
      HomeController.cs
    /Models
    /Views
      /Home
        Index.cshtml
        
  /User
    /Controllers
    /Models
    /Views

Area Controller

[Area("Admin")]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

Area Navigation

<a asp-area="Admin" asp-controller="Home" asp-action="Index">Admin Home</a>

Security

Authentication and Authorization Setup

// In Program.cs
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/Account/Login";
        options.AccessDeniedPath = "/Account/AccessDenied";
    });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => 
        policy.RequireRole("Admin"));
    
    options.AddPolicy("EmployeeOnly", policy => 
        policy.RequireRole("Employee", "Manager", "Admin"));
});

// In the pipeline
app.UseAuthentication();
app.UseAuthorization();

Applying Authorization

// Controller or action level
[Authorize]
public class AccountController : Controller

[Authorize(Roles = "Admin")]
public IActionResult AdminPanel()

[Authorize(Policy = "EmployeeOnly")]
public IActionResult EmployeeArea()

[AllowAnonymous]
public IActionResult Login()

Cross-Site Request Forgery (CSRF) Protection

// In controller action
[ValidateAntiForgeryToken]
[HttpPost]
public async Task<IActionResult> Edit(int id, Movie movie)
<!-- In form -->
<form asp-action="Edit">
    @Html.AntiForgeryToken()
    <!-- form fields -->
</form>

<!-- Or with tag helpers -->
<form asp-action="Edit" method="post">
    <!-- form fields (token included automatically) -->
</form>

Resources

Official Documentation

Community Resources

Tools

Scroll to Top