Routing in ASP.NET Core

What is Routing? Routing in ASP.NET Core is the process of mapping an incoming HTTP request to a particular controller action. When a request is made to an API, routing helps to determine which action should handle it based on the URL and other request parameters.

There are two main types of routing:

  • Attribute Routing: Defined by placing attributes directly on controllers and actions.
  • Conventional Routing: Defined centrally in the Program.cs file using a pattern.

1. IActionResult and How to Use It

What is IActionResult? IActionResult is an interface in ASP.NET Core that represents a result of an action method in a controller. When you define an action in your Web API controller, you often need to specify how the response will be returned to the client.

  • IActionResult provides flexibility, allowing you to return different types of HTTP responses (e.g., Ok, BadRequest, NotFound, etc.).
  • It is very useful when you need to return different response types based on conditions (e.g., success vs. failure).

Examples of IActionResult in Use:

[HttpGet("product/{id}")]
public IActionResult GetProduct(int id)
{
    if (id <= 0)
    {
        return BadRequest("Invalid product ID.");
    }

    var product = new Product { Id = id, Name = "Sample Product", Price = 10.0m };
    if (product == null)
    {
        return NotFound();
    }

    return Ok(product);
}
  • Ok(object value): Returns a status code of 200 OK with the specified data.
  • BadRequest(string message): Returns a 400 Bad Request response with an error message.
  • NotFound(): Returns a 404 Not Found response.

Real-Life Example: In a financial application, you might use IActionResult to return different responses based on whether the transaction was successful (Ok) or if there was an error (BadRequest).

2. MapControllerRoute and How to Use It

What is MapControllerRoute? MapControllerRoute is used to define conventional routing in ASP.NET Core. It allows you to create routes that can map to your controllers and actions using a URL pattern defined in the Program.cs file.

Example of Using MapControllerRoute:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseRouting();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

app.Run();
  • Pattern: "default" is the name of the route, and "pattern" is the format of the URL.
    • {controller=Home}: Indicates the default controller is "Home" if none is specified.
    • {action=Index}: Indicates the default action is "Index" if none is specified.
    • {id?}: The id parameter is optional, denoted by ?.

When to Use MapControllerRoute:

  • When you need to define a central set of routes that are shared across controllers.
  • When your application has a standard URL structure.

3. Attribute Routing vs. Conventional Routing

Attribute Routing:

Routing rules are defined using attributes directly on controller classes and methods.

Example:

[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        return Ok(new Product { Id = id, Name = "Sample Product", Price = 10.0m });
    }
}

[Route("api/products")] defines the base route for the controller.[HttpGet("{id}")] defines the route for an individual action, where {id} is a parameter.

Conventional Routing:

Routing is defined centrally in the Program.cs file using MapControllerRoute.

Example:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

Conventional routing is easier to manage for small projects where there is a predictable pattern.

Difference:

  • Attribute Routing gives you fine-grained control over how each endpoint is exposed and is preferred for building REST APIs since it makes routes more explicit.
  • Conventional Routing is better for larger MVC projects where URLs follow a predictable pattern, and you want to configure them centrally.

Real-Life Example: In an e-commerce application:

  • Attribute Routing might be used for APIs (/api/products/{id}) where each endpoint must be clearly defined.
  • Conventional Routing might be used for the customer-facing part of the website (/products/list or /products/details/{id}) where there is a common structure.

4. Endpoints: What Are They and How to Use Them

What are Endpoints? Endpoints are units of routing information that tell ASP.NET Core where to send a specific request. Each endpoint represents a controller action or a route handler that handles a specific request.

  • An endpoint could be a controller action (/api/products), a Razor page, or a custom delegate.
  • Endpoints are registered using the MapControllers(), MapGet(), MapPost(), etc., methods.

Example:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers(); // Automatically maps all controllers
    endpoints.MapGet("/status", async context =>
    {
        await context.Response.WriteAsync("API is running!");
    });
});
  • MapControllers(): Maps all controllers with attribute routing to endpoints.
  • MapGet(): Creates a simple endpoint to respond to a GET request.

When to Use Endpoints:

  • Use MapControllers() for API controllers.
  • Use MapGet(), MapPost(), etc., for simple endpoints when you need custom, lightweight handlers (e.g., a health check route).

Real-Life Example:

  • In an e-commerce API, endpoints for listing products (GET /api/products) or creating new orders (POST /api/orders) are defined to handle specific functionality.
  • You may also define an endpoint like /status to verify that your API is running properly.

Summary

  • IActionResult: Represents different types of HTTP responses and is used to provide flexibility in returning responses from controller actions.
  • MapControllerRoute: Used for defining conventional routing in a central way in the Program.cs file, making it useful for creating common patterns across multiple controllers.
  • Attribute Routing vs. Conventional Routing:
    • Attribute Routing is more explicit and gives better control for API development.
    • Conventional Routing is centralized and works well for apps with a common structure.
  • Endpoints: Represent routing information and are used to determine which controller action should handle a request. They are registered in UseEndpoints() for better control over the routing pipeline.

Project Structure Overview

  • Controllers: ProductController to handle CRUD operations for products.
  • Routing: Attribute routing in the controller, and conventional routing set up in Program.cs.
  • Middleware: Middleware is added for logging requests.
  • Dependency Injection: IProductService is used to manage product operations.

Code for a Full ASP.NET Core Web API Program

Step 1: Create a Product Model

Create a Product model to represent products in the API.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Step 2: Create a Product Service Interface and Implementation

Define an interface IProductService and its implementation ProductService.

public interface IProductService
{
    List<Product> GetAllProducts();
    Product GetProductById(int id);
    void AddProduct(Product product);
}

public class ProductService : IProductService
{
    private readonly List<Product> _products = new List<Product>
    {
        new Product { Id = 1, Name = "Laptop", Price = 1000.00m },
        new Product { Id = 2, Name = "Phone", Price = 500.00m }
    };

    public List<Product> GetAllProducts() => _products;

    public Product GetProductById(int id) => _products.FirstOrDefault(p => p.Id == id);

    public void AddProduct(Product product) => _products.Add(product);
}

Step 3: Create the Product Controller

The ProductController will handle HTTP requests for managing products.

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet]
    public IActionResult GetProducts()
    {
        var products = _productService.GetAllProducts();
        return Ok(products);
    }

    [HttpGet("{id}")]
    public IActionResult GetProductById(int id)
    {
        var product = _productService.GetProductById(id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }

    [HttpPost]
    public IActionResult AddProduct([FromBody] Product product)
    {
        _productService.AddProduct(product);
        return CreatedAtAction(nameof(GetProductById), new { id = product.Id }, product);
    }
}
  • Attribute Routing: Each action uses attribute routing, such as [HttpGet("{id}")] to match /api/product/{id}.
  • IActionResult: Used to return different types of HTTP responses (e.g., Ok(), NotFound(), CreatedAtAction()).

Step 4: Configure Middleware and Routing in Program.cs

Configure dependency injection, middleware, and routing in the entry point of the application.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddScoped<IProductService, ProductService>();

var app = builder.Build();

// Add middleware for logging requests
app.Use(async (context, next) =>
{
    Console.WriteLine($"Incoming request: {context.Request.Method} {context.Request.Path}");
    await next.Invoke();
});

// Use routing and endpoints
app.UseRouting();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    // Conventional Routing
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

    // Attribute Routing
    endpoints.MapControllers();
});

// Run the application
app.Run();

Step 5: Example Requests

Here's how different components of the program come together when interacting with the API.

  1. Request: GET /api/product
    • Handled by: ProductController.GetProducts()
    • Response: Returns a list of all products (200 OK).
  2. Request: GET /api/product/1
    • Handled by: ProductController.GetProductById(int id)
    • Response: Returns the product with ID 1, or 404 Not Found if it doesn't exist.
  3. Request: POST /api/product
    • Handled by: ProductController.AddProduct(Product product)
    • Body: A JSON object representing the new product.
    • Response: Returns 201 Created with the location of the newly created product.

Explanation of Key Concepts

  1. Attribute Routing vs. Conventional Routing:
    • Attribute Routing: Used in ProductController to specify routes directly on actions.
    • Conventional Routing: Defined in Program.cs using MapControllerRoute, which provides a default route for controllers.
  2. Middleware:
    • Custom middleware in Program.cs logs incoming requests.
    • Middleware is executed in the order it’s added to the pipeline.
  3. Dependency Injection:
    • IProductService is registered in the DI container (builder.Services.AddScoped<IProductService, ProductService>()).
    • The ProductController receives IProductService via constructor injection, which is a best practice to decouple the service logic from the controller.
  4. Endpoints:
    • MapControllers() maps all controllers that use attribute routing.
    • Conventional routing via MapControllerRoute() provides a fallback default route.

Summary

This full example demonstrates how different ASP.NET Core Web API components work together:

  • Controllers handle incoming requests.
  • Routing (attribute and conventional) helps map these requests to the right actions.
  • Middleware processes requests and responses, e.g., logging them.
  • Dependency Injection helps keep code modular and testable.

ניווט במאמר

מאמרים אחרונים

Weekly Tutorial