C# tutorials > Frameworks and Libraries > ASP.NET Core > Controllers and Actions (MVC and Razor Pages)

Controllers and Actions (MVC and Razor Pages)

Understanding Controllers and Actions in ASP.NET Core

This tutorial explains controllers and actions in ASP.NET Core, focusing on both MVC (Model-View-Controller) and Razor Pages paradigms. Controllers and actions are fundamental building blocks for handling HTTP requests and generating responses in web applications.

Basic Controller Structure (MVC)

This code demonstrates a simple controller named HomeController with two actions: Index and Privacy. Each action returns an IActionResult, which represents the result of the action. In this case, they return a ViewResult, indicating that a View should be rendered.

Key elements:

  • The controller class inherits from Controller.
  • Actions are public methods within the controller.
  • The IActionResult return type allows flexibility in returning different types of responses.

using Microsoft.AspNetCore.Mvc;

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

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

Basic Razor Page Structure

This example shows a basic Razor Page. The IndexModel class inherits from PageModel and contains a handler method OnGet that is executed when the page is requested with an HTTP GET. The handler sets a value for the Message property, which is then displayed in the Razor view.

Key elements:

  • The page model class inherits from PageModel.
  • Handler methods like OnGet, OnPost, etc., handle specific HTTP verbs.
  • The @page directive in the Razor view identifies it as a Razor Page.
  • The @model directive specifies the page model type.

// Pages/Index.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace MyWebApp.Pages
{
    public class IndexModel : PageModel
    {
        public string Message { get; set; }

        public void OnGet()
        {
            Message = "Hello, Razor Pages!";
        }
    }
}

// Pages/Index.cshtml
@page
@model MyWebApp.Pages.IndexModel
@{ ViewData["Title"] = "Index"; }

<h1>@Model.Message</h1>

Action Parameters

Actions can accept parameters, allowing you to pass data from the request to the action method. In this example, the Details action accepts an id parameter. This parameter can be passed in the URL (e.g., /Product/Details/123) or through other mechanisms like form data.

Key elements:

  • Parameters are declared in the action method signature.
  • ASP.NET Core automatically binds values from the request (route data, query string, form data) to the parameters based on name and type.

using Microsoft.AspNetCore.Mvc;

namespace MyWebApp.Controllers
{
    public class ProductController : Controller
    {
        public IActionResult Details(int id)
        {
            // Fetch product details based on id
            // Example: var product = _productService.GetProduct(id);
            // return View(product);

            // Dummy return for example
            return Content($"Details for product with ID: {id}");
        }
    }
}

IActionResult Return Types

The IActionResult interface provides a flexible way to return different types of responses from an action. Here are some common IActionResult implementations:

  • ViewResult: Renders a View.
  • ContentResult: Returns a string of content.
  • JsonResult: Returns data as JSON.
  • RedirectResult: Redirects the user to another URL.
  • StatusCodeResult: Returns an HTTP status code.
  • OkResult: Returns a 200 OK status code.
  • BadRequestResult: Returns a 400 Bad Request status code.
  • NotFoundResult: Returns a 404 Not Found status code.

using Microsoft.AspNetCore.Mvc;

namespace MyWebApp.Controllers
{
    public class SampleController : Controller
    {
        public IActionResult GetProduct(int id)
        {
            if (id <= 0)
            {
                return BadRequest("Invalid product ID");
            }

            //Assume product exist
            var product = new { Id = id, Name = "Sample Product", Price = 19.99 };

            return Ok(product);
        }
    }
}

Routing

Routing determines how HTTP requests are mapped to controllers and actions. ASP.NET Core supports both conventional routing and attribute routing.

Conventional Routing: Defines route templates in the Startup.cs file. A common template is {controller}/{action}/{id?}.

Attribute Routing: Uses attributes on controllers and actions to define routes. This provides more control and flexibility.

Attribute Routing Example

This example demonstrates attribute routing. The [Route("api/[controller]")] attribute on the controller class defines a base route for all actions in the controller. The [HttpGet] and [HttpGet("{id}")] attributes on the actions define specific routes for those actions.

[controller] will be replaced by the controller name without the `Controller` suffix (e.g., `Items` in this case).

using Microsoft.AspNetCore.Mvc;

namespace MyWebApp.Controllers
{
    [Route("api/[controller]")]
    public class ItemsController : Controller
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok(new string[] { "item1", "item2" });
        }

        [HttpGet("{id}")]
        public IActionResult Get(int id)
        {
            return Ok($"Item with ID: {id}");
        }
    }
}

Real-Life Use Case: Handling Form Submissions

This example shows how to handle form submissions using controllers and actions. The ContactForm action handles both the GET request (to display the form) and the POST request (when the form is submitted). The ModelState.IsValid property checks if the form data is valid based on validation attributes defined in the ContactModel class.

The `Required` and `EmailAddress` attributes are examples of data annotations used for validation.

// Controller Action
using Microsoft.AspNetCore.Mvc;

namespace MyWebApp.Controllers
{
    public class ContactController : Controller
    {
        [HttpGet]
        public IActionResult ContactForm()
        {
            return View();
        }

        [HttpPost]
        public IActionResult ContactForm(ContactModel model)
        {
            if (ModelState.IsValid)
            {
                // Process the form data (e.g., send an email)
                return RedirectToAction("Success");
            }
            else
            {
                // Redisplay the form with validation errors
                return View(model);
            }
        }

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

// Model (ContactModel.cs)
public class ContactModel
{
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; }

    [EmailAddress(ErrorMessage = "Invalid email address")]
    public string Email { get; set; }

    [Required(ErrorMessage = "Message is required")]
    public string Message { get; set; }
}

Best Practices

  • Keep actions focused: Each action should have a specific purpose.
  • Use appropriate HTTP verbs: Use GET for retrieving data, POST for creating data, PUT/PATCH for updating data, and DELETE for deleting data.
  • Handle errors gracefully: Use try-catch blocks and return appropriate error responses.
  • Use dependency injection: Inject services into your controllers to promote testability and loose coupling.
  • Validate user input: Always validate user input to prevent security vulnerabilities.
  • Follow naming conventions: Use PascalCase for controller and action names.

Interview Tip

When discussing controllers and actions in an interview, be prepared to explain:

  • The difference between MVC controllers and Razor Pages.
  • The role of IActionResult and its various implementations.
  • How routing works in ASP.NET Core.
  • How to handle form submissions and validate user input.
  • Best practices for writing controllers and actions.

When to Use MVC vs. Razor Pages

MVC: Suitable for complex applications with a clear separation of concerns (model, view, controller). Offers greater flexibility and control over the request pipeline.

Razor Pages: Simpler and more lightweight, ideal for page-centric applications where each page has its own logic. Easier to learn and use for basic scenarios.

The choice depends on the complexity and requirements of your application.

Alternatives

While controllers and actions are the standard way to handle requests in ASP.NET Core, alternative approaches exist:

  • Minimal APIs: A streamlined approach for creating APIs with less boilerplate code (introduced in .NET 6). Suitable for simple APIs and microservices.
  • gRPC: A high-performance, open-source framework for remote procedure calls, often used for building microservices.

Pros of Using Controllers and Actions

  • Clear Separation of Concerns: Promotes a well-structured and maintainable codebase.
  • Testability: Controllers can be easily unit tested.
  • Flexibility: Offers a wide range of features and customization options.
  • Mature Framework: MVC has been a popular pattern for many years, resulting in a wealth of resources and community support.

Cons of Using Controllers and Actions

  • More Boilerplate Code: Can require more setup and configuration compared to simpler approaches like Minimal APIs.
  • Steeper Learning Curve: Requires a good understanding of the MVC pattern and ASP.NET Core concepts.
  • Overhead: May introduce some overhead for simpler applications where the full power of MVC is not needed.

FAQ

  • What is the difference between ViewResult and PartialViewResult?

    ViewResult renders a complete view, including the layout. PartialViewResult renders only a partial view, without the layout. Partial views are typically used for reusable UI components within a larger view.
  • How can I pass data from a controller to a view?

    You can pass data using ViewBag, ViewData, or strongly-typed models. ViewBag and ViewData are dynamic containers, while strongly-typed models provide compile-time type safety.
  • How can I implement custom routing?

    You can use attribute routing or conventional routing to define custom routes. Attribute routing provides more control and flexibility, while conventional routing is simpler for basic scenarios. You can also implement custom route constraints to further refine the routing process.