Skip to main content

.NET Controllers

Introduction

Controllers are a fundamental part of ASP.NET Core web applications. They act as traffic directors, handling incoming HTTP requests and determining which actions should be executed and what responses should be returned to the client. Controllers are the "C" in the MVC (Model-View-Controller) pattern, serving as the intermediary between the user interface (View) and the data (Model).

In this tutorial, you'll learn:

  • What controllers are and why they're important
  • How to create and structure controllers
  • Different ways to return responses
  • How to accept and process user input
  • Best practices for controller design

What Are Controllers?

In ASP.NET Core, controllers are classes that handle incoming HTTP requests. They contain public methods called action methods or simply actions, which respond to client requests. Controllers help organize your application's functionality by grouping related actions together.

A controller typically:

  1. Receives and processes HTTP requests
  2. Executes the necessary business logic
  3. Prepares data for the view or formats data for API responses
  4. Returns an appropriate response to the client

Creating Your First Controller

To create a controller in ASP.NET Core, you need to:

  1. Create a class that inherits from the Controller base class
  2. Place it in the "Controllers" folder (by convention)
  3. Name it with the suffix "Controller" (also by convention)

Let's create a simple controller:

csharp
using Microsoft.AspNetCore.Mvc;

namespace MyWebApp.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}

public IActionResult About()
{
return View();
}

public IActionResult Contact()
{
return View();
}
}
}

This simple controller has three action methods: Index, About, and Contact. Each method returns a View() result, which renders a corresponding view template.

Action Methods and Return Types

Action methods can return different types of results. The most common return type is IActionResult, which is an interface that represents different types of action results.

Here are some common return types:

ViewResult (View)

Returns an HTML view to be rendered:

csharp
public IActionResult Index()
{
return View(); // Returns a view with the same name as the action (Index.cshtml)
}

public IActionResult Detail()
{
return View("ProductDetail"); // Returns a specific view (ProductDetail.cshtml)
}

JsonResult

Returns JSON-formatted data (commonly used in APIs):

csharp
public IActionResult GetProduct(int id)
{
var product = new { Id = id, Name = "Laptop", Price = 999.99 };
return Json(product);
}

ContentResult

Returns plain text content:

csharp
public IActionResult Message()
{
return Content("Hello, World!");
}

RedirectResult

Redirects to another URL or action:

csharp
public IActionResult GoToHome()
{
return Redirect("/Home/Index"); // Redirects to a URL
}

public IActionResult GoToAbout()
{
return RedirectToAction("About"); // Redirects to another action
}

public IActionResult GoToContact()
{
return RedirectToAction("Contact", "Support"); // Redirects to an action in another controller
}

StatusCodeResult

Returns HTTP status codes:

csharp
public IActionResult NotFoundExample()
{
return NotFound(); // Returns 404 Not Found
}

public IActionResult OkExample()
{
return Ok(); // Returns 200 OK
}

public IActionResult ServerErrorExample()
{
return StatusCode(500); // Returns 500 Internal Server Error
}

Passing Data to Views

Controllers can pass data to views in several ways. Let's explore the most common approaches:

Using ViewData

ViewData is a dictionary that uses strings as keys:

csharp
public IActionResult Index()
{
ViewData["Message"] = "Welcome to my website!";
ViewData["CurrentTime"] = DateTime.Now;
return View();
}

In your view, you access this data like:

html
<h1>@ViewData["Message"]</h1>
<p>The current time is: @ViewData["CurrentTime"]</p>

Using ViewBag

ViewBag is a dynamic property that provides a more convenient way to pass data:

csharp
public IActionResult Index()
{
ViewBag.Message = "Welcome to my website!";
ViewBag.CurrentTime = DateTime.Now;
return View();
}

In your view:

html
<h1>@ViewBag.Message</h1>
<p>The current time is: @ViewBag.CurrentTime</p>

This approach is cleaner and provides compile-time type checking:

First, define a model:

csharp
public class HomeViewModel
{
public string Message { get; set; }
public DateTime CurrentTime { get; set; }
}

Then, in your controller:

csharp
public IActionResult Index()
{
var model = new HomeViewModel
{
Message = "Welcome to my website!",
CurrentTime = DateTime.Now
};

return View(model);
}

In your view, specify the model at the top:

html
@model MyWebApp.Models.HomeViewModel

<h1>@Model.Message</h1>
<p>The current time is: @Model.CurrentTime</p>

Handling User Input

Controllers can accept user input through various means:

Route Parameters

Route parameters are part of the URL path:

csharp
// URL: /Products/Detail/5
public IActionResult Detail(int id)
{
var product = GetProductById(id); // id would be 5 in this example
return View(product);
}

Query String Parameters

Query string parameters come after the ? in a URL:

csharp
// URL: /Products?category=electronics&sort=price
public IActionResult Index(string category, string sort)
{
var products = GetFilteredProducts(category, sort);
return View(products);
}

Form Data

Processing data submitted via HTML forms:

csharp
[HttpGet]
public IActionResult Login()
{
return View();
}

[HttpPost]
public IActionResult Login(string username, string password)
{
if (IsValidUser(username, password))
{
// Log the user in
return RedirectToAction("Index", "Home");
}

ViewBag.ErrorMessage = "Invalid username or password";
return View();
}

Model Binding

For more complex data, model binding automatically maps form data, route values, and query strings to model objects:

csharp
public class LoginModel
{
public string Username { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
}

[HttpPost]
public IActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
// Process the login
return RedirectToAction("Index", "Home");
}

return View(model);
}

HTTP Verbs and Action Selection

ASP.NET Core controllers use HTTP verb attributes to specify which HTTP methods an action supports:

csharp
// Responds to GET requests
[HttpGet]
public IActionResult Register()
{
return View();
}

// Responds to POST requests
[HttpPost]
public IActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Process registration
return RedirectToAction("Success");
}

return View(model);
}

Common HTTP verb attributes include:

  • [HttpGet] - For retrieving data
  • [HttpPost] - For creating new resources
  • [HttpPut] - For updating existing resources
  • [HttpDelete] - For removing resources
  • [HttpPatch] - For partial updates

API Controllers

For building RESTful APIs, ASP.NET Core provides a specialized ControllerBase class and the [ApiController] attribute:

csharp
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly ProductService _productService;

public ProductsController(ProductService productService)
{
_productService = productService;
}

// GET: api/products
[HttpGet]
public ActionResult<IEnumerable<Product>> GetAllProducts()
{
return _productService.GetAll();
}

// GET: api/products/5
[HttpGet("{id}")]
public ActionResult<Product> GetProduct(int id)
{
var product = _productService.GetById(id);

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

return product;
}

// POST: api/products
[HttpPost]
public ActionResult<Product> CreateProduct(Product product)
{
_productService.Add(product);

return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}

// PUT: api/products/5
[HttpPut("{id}")]
public IActionResult UpdateProduct(int id, Product product)
{
if (id != product.Id)
{
return BadRequest();
}

try
{
_productService.Update(product);
}
catch (KeyNotFoundException)
{
return NotFound();
}

return NoContent();
}

// DELETE: api/products/5
[HttpDelete("{id}")]
public IActionResult DeleteProduct(int id)
{
try
{
_productService.Delete(id);
}
catch (KeyNotFoundException)
{
return NotFound();
}

return NoContent();
}
}

Practical Example: A Complete Blog Controller

Here's a more comprehensive example of a blog controller that handles CRUD operations:

csharp
using Microsoft.AspNetCore.Mvc;
using MyBlog.Models;
using MyBlog.Services;

namespace MyBlog.Controllers
{
public class BlogController : Controller
{
private readonly IBlogService _blogService;

// Use dependency injection to get the blog service
public BlogController(IBlogService blogService)
{
_blogService = blogService;
}

// GET: /Blog
public IActionResult Index()
{
var posts = _blogService.GetAllPosts();
return View(posts);
}

// GET: /Blog/Details/5
public IActionResult Details(int id)
{
var post = _blogService.GetPostById(id);

if (post == null)
{
return NotFound();
}

return View(post);
}

// GET: /Blog/Create
[HttpGet]
public IActionResult Create()
{
return View();
}

// POST: /Blog/Create
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(BlogPost post)
{
if (ModelState.IsValid)
{
_blogService.AddPost(post);
return RedirectToAction(nameof(Index));
}

return View(post);
}

// GET: /Blog/Edit/5
[HttpGet]
public IActionResult Edit(int id)
{
var post = _blogService.GetPostById(id);

if (post == null)
{
return NotFound();
}

return View(post);
}

// POST: /Blog/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, BlogPost post)
{
if (id != post.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
_blogService.UpdatePost(post);
return RedirectToAction(nameof(Index));
}

return View(post);
}

// GET: /Blog/Delete/5
[HttpGet]
public IActionResult Delete(int id)
{
var post = _blogService.GetPostById(id);

if (post == null)
{
return NotFound();
}

return View(post);
}

// POST: /Blog/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(int id)
{
_blogService.DeletePost(id);
return RedirectToAction(nameof(Index));
}
}
}

Best Practices for Controllers

  1. Keep Controllers Focused: Each controller should be responsible for a specific resource or set of closely related resources.

  2. Follow the Single Responsibility Principle: Controllers should delegate business logic to services rather than implementing it themselves.

  3. Use Dependency Injection: Inject services and dependencies rather than creating them inside the controller.

  4. Use Async/Await for I/O Operations: For database access, file operations, or API calls, use async methods:

    csharp
    public async Task<IActionResult> Index()
    {
    var posts = await _blogService.GetAllPostsAsync();
    return View(posts);
    }
  5. Validate Input Data: Use model validation and check ModelState.IsValid before processing input.

  6. Use Action Filters: For cross-cutting concerns like logging, use action filters rather than duplicating code.

  7. Return Appropriate Status Codes: Especially for API controllers, return meaningful HTTP status codes.

  8. Use Route Attributes: Make your routes clear and descriptive:

    csharp
    [Route("api/[controller]")]
    [Route("api/products")]

Summary

Controllers are a crucial component in ASP.NET Core applications, serving as the bridge between the user interface and the business logic. They handle HTTP requests, process user input, and determine the appropriate responses.

In this tutorial, you've learned:

  • The fundamentals of controller architecture in ASP.NET Core
  • How to create controllers and action methods
  • Different ways to return responses (View, JSON, etc.)
  • Various methods for passing data to views
  • How to handle user input through different mechanisms
  • Best practices for designing and implementing controllers

With these foundations, you can now build well-structured, maintainable web applications that effectively handle user interactions and data processing.

Additional Resources

Practice Exercises

  1. Create a simple ProductController that displays a list of products and allows viewing individual product details.

  2. Build a ContactController that handles a contact form submission, validates the input, and sends a confirmation email.

  3. Implement a RESTful API controller for a resource of your choice (e.g., books, movies, recipes).

  4. Enhance an existing controller by adding filtering, sorting, and pagination functionality.

  5. Create a controller that handles file uploads and downloads.



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