Skip to main content

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:

csharp
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:

csharp
// 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:

csharp
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:

cshtml
@model Product

<h1>@Model.Name</h1>
<p>Price: [email protected]</p>

2. Using ViewData or ViewBag

For smaller pieces of data:

csharp
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:

cshtml
<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:

csharp
[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

csharp
// 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

csharp
[Route("orders/{year}/{month}")]
public IActionResult OrdersByMonth(int year, int month)
{
var orders = _orderService.GetOrdersByMonth(year, month);
return View(orders);
}

Form Data

csharp
[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:

csharp
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:

csharp
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:

csharp
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:

csharp
[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:

csharp
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

Exercises

  1. Create a basic CRUD controller for a resource of your choice (e.g., Products, Books, Movies).
  2. Implement form validation in a registration controller that collects user data.
  3. Build an API controller that returns JSON data about weather forecasts.
  4. Create a controller that uses dependency injection to access a service.
  5. 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! :)