Skip to main content

.NET MVC Framework

Introduction

The ASP.NET MVC framework is a lightweight, highly testable presentation framework that is integrated with existing ASP.NET features. It is an alternative to the traditional ASP.NET Web Forms pattern for creating web applications. The MVC (Model-View-Controller) pattern helps developers build applications that separate different aspects of the application (input logic, business logic, and UI logic), while providing a loose coupling between these elements.

In this tutorial, we'll explore the fundamentals of the ASP.NET MVC framework, understand its components, and learn how to build web applications using this architectural pattern.

What is MVC?

MVC stands for Model-View-Controller, which is an architectural pattern that separates an application into three main components:

  • Model: Represents the application data and business logic
  • View: Responsible for rendering UI elements and presenting data to users
  • Controller: Handles user requests, works with the model, and selects a view to render

This separation of concerns makes your application more maintainable, testable, and easier to modify.

Benefits of ASP.NET MVC

  • Clean Separation of Concerns: Separates application logic from the user interface
  • Testability: Controllers can be tested in isolation without the UI
  • Full Control over HTML: Provides complete control over rendered HTML
  • SEO-Friendly: Generates clean URLs that are search engine friendly
  • Integration with Front-end Frameworks: Works well with jQuery, Bootstrap, Angular, etc.
  • Lightweight: Doesn't use ViewState or server-based forms
  • Powerful Routing System: Offers a flexible URL-mapping system

Basic Structure of an MVC Application

When you create a new ASP.NET MVC application, the solution structure typically looks like this:

MyMvcApplication/
├── App_Data/
├── App_Start/
│ ├── BundleConfig.cs
│ ├── FilterConfig.cs
│ └── RouteConfig.cs
├── Controllers/
│ └── HomeController.cs
├── Models/
├── Views/
│ ├── Home/
│ │ ├── Index.cshtml
│ │ └── About.cshtml
│ └── Shared/
│ ├── _Layout.cshtml
│ └── Error.cshtml
├── Content/
│ ├── Site.css
└── Scripts/
└── jquery-x.x.x.js

Let's explore each component in detail.

Models

Models represent the data and business logic of your application. They define the data structure and include validation rules, data access methods, and business rules.

Here's an example of a simple product model:

csharp
public class Product
{
public int Id { get; set; }

[Required]
[StringLength(100)]
public string Name { get; set; }

[Range(0.01, 1000.00)]
[DisplayFormat(DataFormatString = "{0:C}")]
public decimal Price { get; set; }

[StringLength(500)]
public string Description { get; set; }

public int CategoryId { get; set; }

// Navigation property
public virtual Category Category { get; set; }
}

Views

Views are responsible for displaying content to users. They are written using Razor syntax, which combines HTML with C# code.

Here's an example of a simple view that displays product details:

cshtml
@model MyMvcApplication.Models.Product

<h2>Product Details</h2>

<div class="product-detail">
<h3>@Model.Name</h3>
<p class="price">Price: @Model.Price.ToString("C")</p>
<div class="description">
@Model.Description
</div>
<p>Category: @Model.Category.Name</p>
</div>

Layouts and Partial Views

MVC uses layouts to define a common structure for your views and partial views to create reusable UI components.

Layout Example:

cshtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - My MVC Application</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<!-- Navigation elements -->
</nav>

<div class="container body-content">
@RenderBody()

<hr />
<footer>
<p>&copy; @DateTime.Now.Year - My MVC Application</p>
</footer>
</div>

@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>

Partial View Example:

cshtml
@model MyMvcApplication.Models.Product

<div class="product-card">
<h4>@Model.Name</h4>
<p>@Model.Price.ToString("C")</p>
<a href="@Url.Action("Details", "Product", new { id = Model.Id })" class="btn btn-info">Details</a>
</div>

Controllers

Controllers handle user requests, interact with models, and select views to render. They are the central component that coordinates the MVC workflow.

Here's an example of a basic product controller:

csharp
public class ProductController : Controller
{
private readonly ProductDbContext _context;

public ProductController()
{
_context = new ProductDbContext();
}

// GET: /Product
public ActionResult Index()
{
var products = _context.Products.Include(p => p.Category).ToList();
return View(products);
}

// GET: /Product/Details/5
public ActionResult Details(int id)
{
var product = _context.Products.Include(p => p.Category)
.SingleOrDefault(p => p.Id == id);

if (product == null)
{
return HttpNotFound();
}

return View(product);
}

// GET: /Product/Create
public ActionResult Create()
{
ViewBag.CategoryId = new SelectList(_context.Categories, "Id", "Name");
return View();
}

// POST: /Product/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product)
{
if (ModelState.IsValid)
{
_context.Products.Add(product);
_context.SaveChanges();
return RedirectToAction("Index");
}

ViewBag.CategoryId = new SelectList(_context.Categories, "Id", "Name", product.CategoryId);
return View(product);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
_context.Dispose();
}
base.Dispose(disposing);
}
}

Action Methods

Action methods are the public methods in controller classes that handle incoming HTTP requests. They typically return an ActionResult or one of its derived types:

  • ViewResult - Renders a view to the response
  • PartialViewResult - Renders a partial view
  • RedirectResult - Redirects to another URL
  • RedirectToRouteResult - Redirects to another action method
  • JsonResult - Returns a JSON-formatted response
  • FileResult - Returns a file to the client
  • ContentResult - Returns a text response

Routing

Routing is the process of mapping incoming URLs to specific controller action methods. In ASP.NET MVC, routing is configured in the App_Start/RouteConfig.cs file.

csharp
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}

This default route pattern maps URLs in the format /Controller/Action/Id where:

  • controller defaults to "Home" if not specified
  • action defaults to "Index" if not specified
  • id is optional

You can also define custom routes for specific URL patterns:

csharp
routes.MapRoute(
name: "ProductsByCategory",
url: "products/category/{categoryName}",
defaults: new { controller = "Product", action = "ByCategory" }
);

Practical Example: Building a Simple Blog

Let's apply what we've learned by building a simple blog application. We'll create a model for blog posts, controllers to manage them, and views to display them.

1. Creating the Blog Post Model

csharp
public class BlogPost
{
public int Id { get; set; }

[Required]
[StringLength(200)]
public string Title { get; set; }

[Required]
public string Content { get; set; }

[Display(Name = "Publication Date")]
[DataType(DataType.Date)]
public DateTime PublicationDate { get; set; }

[Required]
[StringLength(100)]
public string Author { get; set; }
}

// DbContext
public class BlogDbContext : DbContext
{
public DbSet<BlogPost> BlogPosts { get; set; }
}

2. Creating the Blog Controller

csharp
public class BlogController : Controller
{
private readonly BlogDbContext _context;

public BlogController()
{
_context = new BlogDbContext();
}

// GET: /Blog
public ActionResult Index()
{
var posts = _context.BlogPosts
.OrderByDescending(p => p.PublicationDate)
.ToList();
return View(posts);
}

// GET: /Blog/Details/5
public ActionResult Details(int id)
{
var post = _context.BlogPosts.Find(id);
if (post == null)
{
return HttpNotFound();
}
return View(post);
}

// GET: /Blog/Create
[Authorize] // Only authenticated users can create posts
public ActionResult Create()
{
return View();
}

// POST: /Blog/Create
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult Create(BlogPost blogPost)
{
if (ModelState.IsValid)
{
blogPost.PublicationDate = DateTime.Now;
blogPost.Author = User.Identity.Name;

_context.BlogPosts.Add(blogPost);
_context.SaveChanges();

return RedirectToAction("Index");
}

return View(blogPost);
}

// Other action methods for Edit, Delete, etc.

protected override void Dispose(bool disposing)
{
if (disposing)
{
_context.Dispose();
}
base.Dispose(disposing);
}
}

3. Creating Views for the Blog

Index View (Blog/Index.cshtml)

cshtml
@model IEnumerable<MyMvcApplication.Models.BlogPost>

@{
ViewBag.Title = "Blog Posts";
}

<h2>Latest Blog Posts</h2>

@if (User.Identity.IsAuthenticated)
{
<p>
@Html.ActionLink("Create New Post", "Create", null, new { @class = "btn btn-primary" })
</p>
}

<div class="blog-posts">
@foreach (var post in Model)
{
<article class="blog-post">
<h3>@Html.ActionLink(post.Title, "Details", new { id = post.Id })</h3>
<div class="post-meta">
<span class="date">@post.PublicationDate.ToShortDateString()</span>
<span class="author">by @post.Author</span>
</div>
<div class="post-summary">
@Html.Raw(post.Content.Length > 200
? post.Content.Substring(0, 200) + "..."
: post.Content)
</div>
<p>
@Html.ActionLink("Read more", "Details", new { id = post.Id }, new { @class = "read-more" })
</p>
</article>
<hr />
}
</div>

Details View (Blog/Details.cshtml)

cshtml
@model MyMvcApplication.Models.BlogPost

@{
ViewBag.Title = Model.Title;
}

<div class="blog-post-detail">
<h2>@Model.Title</h2>
<div class="post-meta">
<span class="date">@Model.PublicationDate.ToShortDateString()</span>
<span class="author">by @Model.Author</span>
</div>

<div class="post-content">
@Html.Raw(Model.Content)
</div>

<div class="actions">
@Html.ActionLink("Back to List", "Index")
@if (User.Identity.IsAuthenticated)
{
@Html.ActionLink("Edit", "Edit", new { id = Model.Id })
}
</div>
</div>

Create View (Blog/Create.cshtml)

cshtml
@model MyMvcApplication.Models.BlogPost

@{
ViewBag.Title = "Create New Post";
}

<h2>Create New Blog Post</h2>

@using (Html.BeginForm())
{
@Html.AntiForgeryToken()

<div class="form-horizontal">
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })

<div class="form-group">
@Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
</div>
</div>

<div class="form-group">
@Html.LabelFor(model => model.Content, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextAreaFor(model => model.Content, new { htmlAttributes = new { @class = "form-control" }, rows = 10 })
@Html.ValidationMessageFor(model => model.Content, "", new { @class = "text-danger" })
</div>
</div>

<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-primary" />
@Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-default" })
</div>
</div>
</div>
}

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

Advanced MVC Concepts

Data Annotations and Model Validation

Model validation is an important aspect of MVC applications. ASP.NET MVC provides data annotations to specify validation rules directly on model properties:

csharp
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }

[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }

[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}

Filters

Filters in MVC allow you to run code before or after specific stages in the request processing pipeline. Common filter types include:

  • Authorization Filters: Control access to action methods
  • Action Filters: Execute before and after an action method
  • Result Filters: Execute before and after a result is executed
  • Exception Filters: Handle exceptions that occur in the action method

Example of a custom action filter:

csharp
public class LogActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var log = new SystemLog
{
Controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
Action = filterContext.ActionDescriptor.ActionName,
Timestamp = DateTime.UtcNow,
User = filterContext.HttpContext.User.Identity.Name ?? "Anonymous"
};

// Log to database or file

base.OnActionExecuting(filterContext);
}
}

// Apply the filter to a controller
[LogActionFilter]
public class HomeController : Controller
{
// Action methods
}

Dependency Injection

ASP.NET MVC supports dependency injection, allowing you to inject services into controllers. This makes your code more modular and testable.

csharp
public class ProductController : Controller
{
private readonly IProductRepository _productRepository;

// Constructor injection
public ProductController(IProductRepository productRepository)
{
_productRepository = productRepository;
}

public ActionResult Index()
{
var products = _productRepository.GetAll();
return View(products);
}

// Other action methods
}

Summary

In this tutorial, we've covered the fundamentals of the ASP.NET MVC framework:

  1. Model-View-Controller (MVC) Pattern: Separation of concerns for better maintainability and testability
  2. Models: Represent data and business logic
  3. Views: Display information to users using Razor syntax
  4. Controllers: Handle user requests and coordinate the application flow
  5. Routing: Map URLs to controller actions
  6. Data Validation: Use data annotations to validate model properties
  7. Filters: Intercept the request processing pipeline for cross-cutting concerns
  8. Dependency Injection: Create loosely coupled components

The ASP.NET MVC framework provides a robust foundation for building web applications with clean separation of concerns, full control over HTML, and excellent testability.

Additional Resources

Exercises

  1. Create a simple task management application using ASP.NET MVC that allows users to create, view, edit, and delete tasks.
  2. Extend the blog application we built to include categories and tags for blog posts.
  3. Implement authentication and authorization in your MVC application using ASP.NET Identity.
  4. Create a custom action filter that logs all exceptions to a database.
  5. Build a product catalog with search functionality and pagination.

By practicing with these exercises, you'll gain hands-on experience with ASP.NET MVC and improve your web development skills.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)