.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:
- Receives and processes HTTP requests
- Executes the necessary business logic
- Prepares data for the view or formats data for API responses
- Returns an appropriate response to the client
Creating Your First Controller
To create a controller in ASP.NET Core, you need to:
- Create a class that inherits from the
Controller
base class - Place it in the "Controllers" folder (by convention)
- Name it with the suffix "Controller" (also by convention)
Let's create a simple controller:
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:
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):
public IActionResult GetProduct(int id)
{
var product = new { Id = id, Name = "Laptop", Price = 999.99 };
return Json(product);
}
ContentResult
Returns plain text content:
public IActionResult Message()
{
return Content("Hello, World!");
}
RedirectResult
Redirects to another URL or action:
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:
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:
public IActionResult Index()
{
ViewData["Message"] = "Welcome to my website!";
ViewData["CurrentTime"] = DateTime.Now;
return View();
}
In your view, you access this data like:
<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:
public IActionResult Index()
{
ViewBag.Message = "Welcome to my website!";
ViewBag.CurrentTime = DateTime.Now;
return View();
}
In your view:
<h1>@ViewBag.Message</h1>
<p>The current time is: @ViewBag.CurrentTime</p>
Using Strongly Typed Models (Recommended)
This approach is cleaner and provides compile-time type checking:
First, define a model:
public class HomeViewModel
{
public string Message { get; set; }
public DateTime CurrentTime { get; set; }
}
Then, in your controller:
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:
@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:
// 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:
// 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:
[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:
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:
// 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:
[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:
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
-
Keep Controllers Focused: Each controller should be responsible for a specific resource or set of closely related resources.
-
Follow the Single Responsibility Principle: Controllers should delegate business logic to services rather than implementing it themselves.
-
Use Dependency Injection: Inject services and dependencies rather than creating them inside the controller.
-
Use Async/Await for I/O Operations: For database access, file operations, or API calls, use async methods:
csharppublic async Task<IActionResult> Index()
{
var posts = await _blogService.GetAllPostsAsync();
return View(posts);
} -
Validate Input Data: Use model validation and check
ModelState.IsValid
before processing input. -
Use Action Filters: For cross-cutting concerns like logging, use action filters rather than duplicating code.
-
Return Appropriate Status Codes: Especially for API controllers, return meaningful HTTP status codes.
-
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
- Official ASP.NET Core Documentation on Controllers
- ASP.NET Core MVC Tutorial
- Web API with ASP.NET Core
Practice Exercises
-
Create a simple ProductController that displays a list of products and allows viewing individual product details.
-
Build a ContactController that handles a contact form submission, validates the input, and sends a confirmation email.
-
Implement a RESTful API controller for a resource of your choice (e.g., books, movies, recipes).
-
Enhance an existing controller by adding filtering, sorting, and pagination functionality.
-
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! :)