Skip to main content

Complete SMS MVC Application

Level: Advanced

ℹ️ What You'll Learn
  • MVC folder structure: Controllers/, Views/, Models/, Services/, Program.cs (entry point)
  • Controllers organize by entity: StudentsController, TeachersController, ExamsController, FeesController (each has CRUD actions)
  • Views folder mirrors controllers: Views/Students/, Views/Teachers/ (one folder per controller)
  • CRUD workflow: Index (list) → Details (view one) → Create (form) → POST Create (save) → RedirectToAction → Edit (form) → POST Edit (update) → Delete (confirm) → POST Delete (remove)
  • GET actions show forms/data: public IActionResult Index() returns list, public IActionResult Create() returns empty form
  • POST actions process forms: [HttpPost] public async Task<IActionResult> Create(Student student) validates + saves
  • Validation flow: POST → ModelState.IsValid check → If invalid, return View with errors → If valid, save + redirect
  • Navigation menu: _Layout.cshtml (shared header with links to Students, Teachers, Exams, Fees pages)
  • Error handling: If student not found, return NotFound(); if validation fails, return View with error messages
  • Database integration: DbContext injected into service, service calls await _context.SaveChangesAsync()
  • School Management complete flow: User opens /students → Index lists all → Clicks "Create" → Form → Enters data → Submit → Validation → Save → Redirect to Details
  • Error feedback: User sees validation messages on form, success message after redirect (TempData), or error page if database fails
  • Common mistakes: Not checking ModelState, forgetting await on async operations, not handling NotFound cases

Project Structure

SMS.Web/
├── Controllers/
│ ├── HomeController.cs
│ ├── StudentsController.cs
│ ├── ExamsController.cs
│ └── FeesController.cs
├── Views/
│ ├── Home/
│ │ └── Index.cshtml
│ ├── Students/
│ │ ├── Index.cshtml
│ │ ├── Details.cshtml
│ │ ├── Create.cshtml
│ │ └── Edit.cshtml
│ ├── Exams/
│ │ └── Index.cshtml
│ ├── Fees/
│ │ └── Index.cshtml
│ └── Shared/
│ └── _Layout.cshtml
├── Models/
│ ├── Student.cs
│ ├── Exam.cs
│ └── Fee.cs
├── Services/
│ ├── IStudentService.cs
│ ├── StudentService.cs
│ └── DbContext (EF Core)
├── Program.cs
└── appsettings.json

Program.cs Setup

var builder = WebApplicationBuilder.CreateBuilder(args);

// Add MVC
builder.Services.AddControllersWithViews();

// Database
builder.Services.AddDbContext<SmsDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
);

// Services
builder.Services.AddScoped<IStudentService, StudentService>();
builder.Services.AddScoped<IExamService, ExamService>();
builder.Services.AddScoped<IFeeService, FeeService>();

// Logging
builder.Services.AddLogging();

var app = builder.Build();

// Middleware
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

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

app.Run();

Layout with Navigation

File: Views/Shared/_Layout.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - SMS</title>
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
</head>
<body>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-dark bg-dark mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-controller="Home" asp-action="Index">
📚 School Management System
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link" asp-controller="Students" asp-action="Index">
Students
</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="Exams" asp-action="Index">
Exams
</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="Fees" asp-action="Index">
Fees
</a>
</li>
</ul>
</div>
</div>
</nav>

<main role="main" class="pb-3">
<div class="container">
@RenderBody()
</div>
</main>

<footer class="border-top footer text-muted mt-5 pt-3">
<div class="container">
&copy; 2024 NexCoding Academy | School Management System
</div>
</footer>

<script src="~/lib/jquery/jquery.min.js"></script>
<script src="~/lib/bootstrap/js/bootstrap.bundle.min.js"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Students Controller

using Microsoft.AspNetCore.Mvc;
using SMS.Services;
using SMS.Models;

[Route("[controller]")]
public class StudentsController : Controller
{
private readonly IStudentService _service;
private readonly ILogger<StudentsController> _logger;

public StudentsController(IStudentService service, ILogger<StudentsController> logger)
{
_service = service;
_logger = logger;
}

[HttpGet]
public async Task<IActionResult> Index()
{
_logger.LogInformation("Fetching all students");
var students = await _service.GetStudentsAsync();
return View(students);
}

[HttpGet("{id}")]
public async Task<IActionResult> Details(int id)
{
var student = await _service.GetStudentAsync(id);
if (student == null)
{
_logger.LogWarning("Student {Id} not found", id);
return NotFound();
}
return View(student);
}

[HttpGet("create")]
public IActionResult Create()
{
return View();
}

[HttpPost("create")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Student student)
{
if (!ModelState.IsValid)
return View(student);

_logger.LogInformation("Creating student: {Name}", student.Name);
await _service.CreateStudentAsync(student);

return RedirectToAction(nameof(Details), new { id = student.Id });
}

[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(int id)
{
var student = await _service.GetStudentAsync(id);
if (student == null)
return NotFound();

return View(student);
}

[HttpPost("edit/{id}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, Student student)
{
if (id != student.Id)
return NotFound();

if (!ModelState.IsValid)
return View(student);

_logger.LogInformation("Updating student: {Id}", id);
await _service.UpdateStudentAsync(id, student);

return RedirectToAction(nameof(Index));
}

[HttpPost("delete/{id}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
{
var student = await _service.GetStudentAsync(id);
if (student == null)
return NotFound();

_logger.LogInformation("Deleting student: {Id}", id);
await _service.DeleteStudentAsync(id);

return RedirectToAction(nameof(Index));
}
}

Students List View

File: Views/Students/Index.cshtml

@model List<Student>

@{
ViewData["Title"] = "Students";
}

<div class="row mb-3">
<div class="col-md-6">
<h1>Students</h1>
</div>
<div class="col-md-6 text-end">
<a asp-action="Create" class="btn btn-primary">+ Add Student</a>
</div>
</div>

@if (Model.Any())
{
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>Name</th>
<th>Roll Number</th>
<th>Class</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var student in Model)
{
<tr>
<td>
<strong>@student.Name</strong>
</td>
<td>@student.RollNumber</td>
<td>
<span class="badge bg-info">@student.ClassName</span>
</td>
<td>
@if (student.Status == StudentStatus.Active)
{
<span class="badge bg-success">Active</span>
}
else if (student.Status == StudentStatus.Inactive)
{
<span class="badge bg-warning">Inactive</span>
}
else
{
<span class="badge bg-secondary">Graduated</span>
}
</td>
<td>
<a asp-action="Details" asp-route-id="@student.Id"
class="btn btn-sm btn-info">View</a>
<a asp-action="Edit" asp-route-id="@student.Id"
class="btn btn-sm btn-warning">Edit</a>
<form asp-action="Delete" asp-route-id="@student.Id"
method="post" style="display:inline;">
<button type="submit" class="btn btn-sm btn-danger"
onclick="return confirm('Delete this student?');">
Delete
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
}
else
{
<div class="alert alert-info">
No students found. <a asp-action="Create">Create one</a>
</div>
}

Create Form View

File: Views/Students/Create.cshtml

@model Student

@{
ViewData["Title"] = "Create Student";
}

<h1>Add New Student</h1>

<form asp-action="Create" method="post" class="form-horizontal mt-4">
@Html.AntiForgeryToken()

<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label asp-for="Name" class="form-label"></label>
<input asp-for="Name" class="form-control" placeholder="Full name" />
<span asp-validation-for="Name" class="text-danger small"></span>
</div>

<div class="form-group mb-3">
<label asp-for="RollNumber" class="form-label"></label>
<input asp-for="RollNumber" class="form-control"
placeholder="SMS-2024-001" />
<span asp-validation-for="RollNumber" class="text-danger small"></span>
</div>

<div class="form-group mb-3">
<label asp-for="ClassName" class="form-label"></label>
<input asp-for="ClassName" class="form-control" placeholder="10-A" />
<span asp-validation-for="ClassName" class="text-danger small"></span>
</div>

<div class="form-group mb-3">
<label asp-for="Status" class="form-label"></label>
<select asp-for="Status" class="form-select">
<option value="">Select Status</option>
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
<option value="Graduated">Graduated</option>
</select>
</div>

<div class="form-group">
<button type="submit" class="btn btn-primary">Create</button>
<a asp-action="Index" class="btn btn-secondary">Cancel</a>
</div>
</div>
</div>
</form>

@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}

Key Takeaways

  • Layered: Controller → Service → DbContext
  • Routing: {controller}/{action}/{id}
  • Forms: POST with [ValidateAntiForgeryToken]
  • Validation: Attributes + ModelState.IsValid
  • Views: Razor templates with tag helpers
  • Navigation: asp-controller and asp-action tags
  • Error handling: NotFound(), View(), Redirect()
  • Logging: ILogger injection and logging
💡 MVC Best Practices

Keep controllers thin. Logic goes in services. Views are presentation only.

🤖Use AI to Learn Faster

Use ChatGPT, Claude, or Copilot to go deeper on Complete SMS MVC Application. Try these prompts:

  • "How do I structure a large MVC project?"
  • "When should I use services vs controllers?"
  • "How do I handle errors in MVC?"
  • "What's the complete CRUD flow?"
  • "Quiz me on MVC"

💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.

nexcoding.in