C# Controllers
Introduction
Controllers are a fundamental component of ASP.NET Core web applications, serving as the central orchestrators between the user interface and the backend data. In the Model-View-Controller (MVC) architectural pattern, controllers represent the "C" - they handle incoming HTTP requests, process user input, interact with models to retrieve or manipulate data, and ultimately determine which view to render back to the client.
If you're building web applications with C#, understanding controllers is essential for creating well-structured, maintainable code that follows industry-standard patterns.
What are Controllers?
Controllers in C# web applications:
- Handle and respond to user requests
- Process input data from forms or query strings
- Coordinate between the Model (data) and View (user interface)
- Return appropriate responses (HTML pages, JSON data, files, etc.)
- Manage application flow and navigation
In ASP.NET Core, controllers are implemented as classes that inherit from the base Controller
class or implement the IController
interface.
Creating Your First Controller
Let's create a simple controller to understand the basic structure:
using Microsoft.AspNetCore.Mvc;
namespace MyWebApp.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View(); // Returns the default Index view
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
}
}
This example shows a standard HomeController with three action methods: Index, About, and Contact. Each method returns a View, which renders an HTML page to the client.
Controller Actions
Controller actions are public methods within a controller class that handle incoming requests. Each action typically corresponds to a specific URL endpoint in your application.
Action Return Types
Actions can return different types of results:
// Return a view (HTML)
public IActionResult Details(int id)
{
var product = _productService.GetProduct(id);
return View(product);
}
// Return JSON data
public IActionResult GetProductData(int id)
{
var product = _productService.GetProduct(id);
return Json(product);
}
// Redirect to another action
public IActionResult ProcessSubmission()
{
// Process form data
return RedirectToAction("ThankYou");
}
// Return a file
public IActionResult DownloadReport()
{
byte[] fileBytes = _reportGenerator.GeneratePdfReport();
return File(fileBytes, "application/pdf", "report.pdf");
}
// Return a specific status code
public IActionResult ProductNotFound()
{
return NotFound();
}
Passing Data to Views
Controllers can pass data to views in several ways:
1. Using Model Objects
The most direct way is to pass a strongly typed model to the view:
public IActionResult Details(int id)
{
// Get product from database
var product = _productRepository.GetById(id);
// Pass product directly to view
return View(product);
}
In the corresponding view, you can access this model:
@model Product
<h1>@Model.Name</h1>
<p>Price: [email protected]</p>
2. Using ViewData or ViewBag
For smaller pieces of data:
public IActionResult About()
{
ViewData["Title"] = "About Us";
ViewData["Message"] = "Learn more about our company.";
// ViewBag is a dynamic property alternative to ViewData
ViewBag.Founded = 2010;
return View();
}
In the view:
<h1>@ViewData["Title"]</h1>
<p>@ViewData["Message"]</p>
<p>Founded in: @ViewBag.Founded</p>
Routing in Controllers
ASP.NET Core provides multiple ways to configure URL routes for your controllers:
Attribute Routing
Modern applications often use attribute routing, where routes are defined directly on controller actions:
[Route("api/[controller]")]
public class ProductsController : Controller
{
[HttpGet] // Responds to GET /api/products
public IActionResult GetAll()
{
// Return all products
return Ok(_products);
}
[HttpGet("{id}")] // Responds to GET /api/products/5
public IActionResult GetById(int id)
{
// Return specific product
var product = _products.FirstOrDefault(p => p.Id == id);
if (product == null)
return NotFound();
return Ok(product);
}
[HttpPost] // Responds to POST /api/products
public IActionResult Create([FromBody] Product product)
{
// Create a new product
_products.Add(product);
return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}
}
Controller Parameters
Controllers can accept parameters from various sources:
Query String
// URL: /products/search?query=laptop&maxPrice=1000
public IActionResult Search(string query, decimal? maxPrice)
{
var results = _productService.Search(query, maxPrice);
return View(results);
}
Route Data
[Route("orders/{year}/{month}")]
public IActionResult OrdersByMonth(int year, int month)
{
var orders = _orderService.GetOrdersByMonth(year, month);
return View(orders);
}
Form Data
[HttpPost]
public IActionResult Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
// Process the registration
_userService.Register(model);
return RedirectToAction("Success");
}
// If we got this far, something failed; redisplay form
return View(model);
}
Model Binding and Validation
ASP.NET Core's model binding automatically maps input data (form fields, route values, query strings) to action parameters:
public class ProductController : Controller
{
[HttpPost]
public IActionResult Create(ProductViewModel product)
{
if (!ModelState.IsValid)
{
// Return the view with validation errors
return View(product);
}
// Process the valid product
_productService.AddProduct(product);
return RedirectToAction("Index");
}
}
The ProductViewModel
might look like this:
public class ProductViewModel
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[Range(0.01, 10000)]
public decimal Price { get; set; }
[StringLength(500)]
public string Description { get; set; }
}
Dependency Injection in Controllers
Controllers often need to interact with services like databases, logging, or external APIs. ASP.NET Core uses dependency injection to provide these services:
public class OrderController : Controller
{
private readonly IOrderService _orderService;
private readonly ILogger<OrderController> _logger;
// Constructor injection
public OrderController(
IOrderService orderService,
ILogger<OrderController> logger)
{
_orderService = orderService;
_logger = logger;
}
public IActionResult Index()
{
_logger.LogInformation("Retrieving all orders");
var orders = _orderService.GetAllOrders();
return View(orders);
}
}
API Controllers
For building RESTful APIs, you can use the ControllerBase
class and the [ApiController]
attribute:
[Route("api/[controller]")]
[ApiController]
public class CustomersController : ControllerBase
{
private readonly ICustomerRepository _repository;
public CustomersController(ICustomerRepository repository)
{
_repository = repository;
}
// GET: api/customers
[HttpGet]
public ActionResult<IEnumerable<Customer>> GetAll()
{
return _repository.GetAll().ToList();
}
// GET: api/customers/5
[HttpGet("{id}")]
public ActionResult<Customer> GetById(int id)
{
var customer = _repository.GetById(id);
if (customer == null)
{
return NotFound();
}
return customer;
}
// POST: api/customers
[HttpPost]
public ActionResult<Customer> Create(Customer customer)
{
_repository.Add(customer);
_repository.SaveChanges();
return CreatedAtAction(
nameof(GetById),
new { id = customer.Id },
customer);
}
}
This API controller will automatically:
- Validate models
- Infer parameter binding sources
- Return appropriate HTTP status codes
- Format response data (usually JSON)
Real-world Example: Shopping Cart Controller
Let's see a practical example of a shopping cart controller that demonstrates many of the concepts we've covered:
public class CartController : Controller
{
private readonly IProductService _productService;
private readonly ICartService _cartService;
public CartController(
IProductService productService,
ICartService cartService)
{
_productService = productService;
_cartService = cartService;
}
// GET: /Cart
public IActionResult Index()
{
// Get the current cart from session or database
var cart = _cartService.GetCart(GetCartId());
var model = new CartViewModel
{
CartItems = cart.Items,
CartTotal = cart.GetTotal()
};
return View(model);
}
// POST: /Cart/AddItem
[HttpPost]
public IActionResult AddItem(int productId, int quantity)
{
// Validate the product exists
var product = _productService.GetById(productId);
if (product == null)
{
return NotFound();
}
// Add to cart
_cartService.AddToCart(GetCartId(), product, quantity);
// Redirect to cart page
return RedirectToAction("Index");
}
// POST: /Cart/UpdateItem
[HttpPost]
public IActionResult UpdateItem(int productId, int quantity)
{
_cartService.UpdateQuantity(GetCartId(), productId, quantity);
return RedirectToAction("Index");
}
// POST: /Cart/RemoveItem
[HttpPost]
public IActionResult RemoveItem(int productId)
{
_cartService.RemoveFromCart(GetCartId(), productId);
return RedirectToAction("Index");
}
// GET: /Cart/Checkout
public IActionResult Checkout()
{
var cart = _cartService.GetCart(GetCartId());
if (!cart.Items.Any())
{
ModelState.AddModelError("", "Your cart is empty!");
return RedirectToAction("Index");
}
var model = new CheckoutViewModel
{
CartTotal = cart.GetTotal()
};
return View(model);
}
// Helper method to get cart ID
private string GetCartId()
{
// Check if user is logged in
if (User.Identity.IsAuthenticated)
{
// Use user ID as cart ID
return User.FindFirst(ClaimTypes.NameIdentifier).Value;
}
else
{
// Use session ID as cart ID
var cartId = HttpContext.Session.GetString("CartId");
if (string.IsNullOrEmpty(cartId))
{
cartId = Guid.NewGuid().ToString();
HttpContext.Session.SetString("CartId", cartId);
}
return cartId;
}
}
}
This controller:
- Uses dependency injection for services
- Handles different HTTP verbs (GET, POST)
- Manages a shopping cart using sessions
- Demonstrates user authentication awareness
- Shows redirection between actions
- Implements common e-commerce functionality
Summary
Controllers are the backbone of C# web applications, handling user requests and orchestrating the application flow. We've covered:
- The basic structure and role of controllers in MVC architecture
- Creating controller actions and returning different result types
- Passing data to views using models, ViewData, and ViewBag
- Configuring routes for your controllers
- Accepting and processing parameters from various sources
- Implementing model binding and validation
- Using dependency injection to access services
- Building API controllers for RESTful services
- A real-world shopping cart implementation
As you continue developing with C# and ASP.NET Core, you'll find controllers to be one of the most important components you work with. They provide the structure and organization necessary for building scalable, maintainable web applications.
Additional Resources
- Official ASP.NET Core Documentation on Controllers
- ASP.NET Core MVC Pattern
- Routing in ASP.NET Core
- API Controllers in ASP.NET Core
Exercises
- Create a basic CRUD controller for a resource of your choice (e.g., Products, Books, Movies).
- Implement form validation in a registration controller that collects user data.
- Build an API controller that returns JSON data about weather forecasts.
- Create a controller that uses dependency injection to access a service.
- Implement a search controller that accepts multiple parameters (query string, filters, pagination).
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)