Routing in ASP.NET Core Web API

Routing in ASP.NET Core Web API Application

In this article, I will discuss Routing in ASP.NET Core Web API Applications. Please read our previous article discussing Models in ASP.NET Core Web API Applications. In this article, I will give you a comprehensive overview of Routing in ASP.NET Core, covering its mechanisms, types, configuration, and best practices.

Why Routing in ASP.NET Core Web API?

Let us understand why Routing in ASP.NET Core Web API Application with an example. Please have a look at the following image. On the right-hand side, we have the server, and within the server, we have deployed our ASP.NET Core Web API application. Inside the ASP.NET Core Web API Application, assume we have three controllers, each containing some action methods. On the left-hand side, we have the clients. The client can be a Web Browser, Postman, Fiddler, Swagger, Mobile APP, Desktop Application, etc.

Suppose we are sending a request from the client to the server. As we already discussed, the request must go through the Request Processing Pipeline. If everything is fine, the ASP.NET Core Framework navigates that request to the controller action method. Then, the controller action method handles that request and generates the response. The generated response is sent back to the client who initially made the request, as shown in the image below.

Why Routing in ASP.NET Core Web API?

Here, we need to understand how the application will come to know which request will be mapped to which controller action method. Basically, the mapping between the URL and resource (controller action method) is nothing but the concept of Routing. 

What Is Routing in ASP.NET Core?

Routing in ASP.NET Core is a concept that is used to map the incoming HTTP requests to specific controller actions in our application.  When a request comes in, the Routing Engine inspects the URL (and possibly other data like HTTP method, Route Data, etc.) and determines which action method of which controller should handle it. For a better understanding, please have a look at the following diagram.

How Does Routing Work in ASP.NET Core?

Let us understand how Routing Works based on the above diagram:

Incoming HTTP Request:

A client (e.g., a browser, Postman, Swagger, Fiddler, or a mobile app) sends an HTTP request to the server. The HTTP request typically contains:

  • HTTP Method: Specifies the type of operation (e.g., GET, POST, PUT, PATCH, DELETE).
  • URL: The address indicating the resource being accessed (e.g., https://example.com/api/customers/5).
  • Headers: Metadata about the request (e.g., Authorization, Content-Type, Accept).
  • Query String (Optional): Additional parameters sent as part of the URL (e.g., ?search=abc).
  • Request Body (Optional): Data sent with POST, PUT, or PATCH requests (e.g., JSON payload for creating or updating a resource).
URL Parsing:

When the server receives the request, the URL is parsed into its components to determine the resource being requested by the client. This will be done using the Routing Middleware component. For example, the URL https://example.com/api/customers/5 is broken down into:

  • Scheme: https (protocol used).
  • Host: example.com (the server or domain).
  • Path: /api/customers/5 (identifies the resource or endpoint).
  • Query String: Additional parameters if provided (e.g., ?search=abc).
Find Matching Route:

The routing engine in ASP.NET Core examines the parsed URL and tries to match it against the application’s route table. A Route Table is a collection of route patterns defined in the application via the attribute or conventional routing. Each route pattern specifies:

  • Controller name
  • Action method name
  • HTTP method(s)
  • Route parameters
  • Defaults and constraints

The routing engine compares the URL segments (e.g., /api/customers/5) against these route patterns defined in the Route table. It determines which controller and action should handle the request.

Route Found?

At this stage, the routing engine checks if a matching route exists in the route table.

No Match Found (HTTP 404 Error): If no matching route is found, the application returns an HTTP 404 Not Found error, indicating that the requested resource or page cannot be found.

Match Found (Process the Request): If a matching route is found:

  • The Controller Factory creates an instance of the selected controller.
  • The framework invokes the matching action method, which processes the request and generates a response.
Request Processing

Once the request matches a route, the following steps occur:

  • Model Binding: Extracts data from the route, query string, headers, or body and binds it to action parameters or models.
  • Action Filters: Executes any filters defined for the action (e.g., authentication, logging).
  • Action Execution: Runs the logic in the action method to process the request.
  • Response Generation: Prepares a response (e.g., JSON, HTML) to send to the client.
Response Sent:

The result of the action method (e.g., a View, JSON, XML) is sent back to the client as the HTTP response, which includes:

  • Status Code: Indicates the outcome (e.g., 200 OK, 404 Not Found, 500 Internal Server Error).
  • Content: The actual response data (e.g., HTML, JSON, or plain text).
  • Headers: Metadata about the response (e.g., Content-Type, Cache-Control).
What are the Different Types of Routing Supported by ASP.NET Core?

ASP.NET Core supports two primary routing strategies:

  • Convention-Based Routing: Configured globally, typically in the Program.cs file.
  • Attribute-Based Routing: Configured using attributes applied directly to controllers and action methods.

Note: Both strategies can be used independently or in combination, depending on the application’s needs.

What is Conventional Based Routing in ASP.NET Core Application?

Convention-based routing defines URL patterns and maps them to controller actions based on predefined conventions. This approach centralizes route configuration, making it easier to manage, especially in applications with numerous controllers and actions. This routing style is ideal for situations where the routes follow a predictable structure, making it easy to manage and maintain. In the Program.cs class file, convention-based routing is typically configured using the MapControllerRoute method. The Syntax is given below:

What is Conventional Based Routing in ASP.NET Core Application?

This defines a default pattern for routes, where {controller} is the controller name, {action} is the action method, and {id?} is an optional route parameter.

Key Characteristics:

The following are the Key Characteristics of Convention-Based Routing in ASP.NET Core:

  • Routes are defined in one place, usually in Program.cs.
  • Follows a consistent URL structure, enhancing readability and maintainability.
  • It is ideal for applications where many endpoints follow similar patterns. That means Conventional Routing is often preferred for applications with a consistent URL structure across many controllers and actions.
  • Commonly used in ASP.NET Core MVC Web Applications.
What is Attribute-Based Routing in ASP.NET Core Application?

Attribute-based routing allows developers to define routes directly on controllers and action method levels using attributes. This approach provides more flexibility and control over the routing behavior, making it suitable for APIs with complex or unique URL structures. The following is the syntax to use Attribute-Based Routing in ASP.NET Core Web API:

What is Attribute-Based Routing in ASP.NET Core Application?

Here, the Route attribute defines the base URL for the controller. The HttpGet attribute defines the route and HTTP verb for the GetProduct action.

Key Characteristics:

The following are the Key Characteristics of Attribute-Based Routing in ASP.NET Core Applications:

  • Routes are defined alongside their corresponding actions, improving clarity.
  • Enables complex and customized routes for specific needs.
  • Allows individual actions to have distinct routes independent of global patterns.
  • Commonly used in ASP.NET Core Web API Restful Services.
Example to Understand Routing in ASP.NET Core Web API:

First, create a new ASP.NET Core Web API Application named RoutingInASPNETCoreWebAPI. The ASP.NET Core Web API Project should be created with the following file and folder structure.

Example to Understand Routing in ASP.NET Core Web API

Understanding Program.cs Class file:

If you open the Program class, you will see the following code: it registered the Web API Services, i.e., AddControllers(), to the dependency injection container and the Routing Middleware Component, i.e., app.MapControllers(), to the Request Processing pipeline.

namespace RoutingInASPNETCoreWebAPI
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.

            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}
Adding a New API Empty Controller:

Let’s add a Controller named Employee Controller within the Controllers folder. To do so, right-click on the Controllers folder and then select Add => Controller from the context menu, which will open the following Add New Scaffolded item window. From the left side, choose API, and from the middle pane, select API Controller – Empty and click on the Add button, as shown in the image below.

What Is Routing in ASP.NET Core?

From the next window, provide the Controller name as EmployeeController and click the Add button, as shown in the image below.

What are the Different Types of Routing Supported by ASP.NET Core?

Once you click the Add button, a class file named EmployeeController.cs will be added to the Controllers folder. The following default code is inside the EmployeeController.cs class file.

Routing in ASP.NET Core

Implementing Attribute Routing in ASP.NET Core Web API Application:

Now, let us add two action methods within the EmployeeController class. Please don’t concentrate on the return type and the data we return from the action method; instead, focus on the Routing concept.

We want to invoke the GetAllEmployees method with the URL /Emp/All and the GetEmployeeById method with the URL /Emp/ById/102. To achieve this, we need to use the Route Attribute and decorate the action GetAllEmployees and GetEmployeeById method as [Route(“Emp/All”)] and [Route(“Emp/ById/{Id}”)] respectively. So, modify the EmployeeController class as shown below.

using Microsoft.AspNetCore.Mvc;
namespace RoutingInASPNETCoreWebAPI.Controllers
{
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        [Route("Emp/All")]
        [HttpGet]
        public string GetAllEmployees()
        {
            return "Response from GetAllEmployees Method";
        }

        [Route("Emp/ById/{Id}")]
        [HttpGet]
        public string GetEmployeeById(int Id)
        {
            return $"Response from GetEmployeeById Method Id: {Id}";
        }
    }
}

Note: While HTTP verb attributes (e.g., [HttpGet]) are optional, using them for clarity and to ensure proper integration with tools like Swagger is recommended. If we don’t specify the HTTP verb, such as HttpGet, HttpPost, HttpPut, etc., then it will be HttpGet by default. But, if you don’t decorate the action method with an HTTP verb, the application will work as expected, but Swagger will not work.

Run the application and access the action method using the URL we configured with Route Attribute. You should get the response as expected, as shown in the below image.

Implementing Attribute Routing in ASP.NET Core Web API Application

What is a Route Table in ASP.NET Core?

When the application starts, ASP.NET Core scans for controllers and their route attributes, building a Route Table. This table contains entries for each route pattern. Each Route Entry in the Route Table typically includes the following components:

  • Route Template: Defines the URL pattern (e.g., Emp/All). This specifies how the incoming URL should be structured to match this route.
  • Defaults: Default values for route parameters if they are not provided in the URL. In api/{controller=Home}/{action=Index}/{id?}, Home and Index are default values for controller and action, respectively.
  • Constraints: Conditions that route parameters must satisfy for the route to be considered a match. For example, {id:int} ensures that the id parameter is an integer.
  • Handlers: A delegate points to the controller action that will handle the request. The corresponding controller action method will be executed when the delegate is invoked. There is a separate handler for each endpoint.
How to Implement Conventional-Based Routing in ASP.NET Core Web API:

If you want to use Conventional-Based Routing, please add the MapControllerRoute middleware component to the Program.cs class file. Here, we are configuring the Route Pattern as api/{controller}/{action}/{id?} where id is the optional parameter.

app.MapControllerRoute(
    name: "default",
    pattern: "api/{controller}/{action}/{id?}");
Modifying the Employee Controller:

Next, modify the Employee Controller as follows. Here, we remove the Route Attribute from the Controller and the action method levels. You also need to remove the ApiController attribute decorated with the Controller class. The ApiController attribute will force the action method to use the Route attribute.

using Microsoft.AspNetCore.Mvc;
namespace RoutingInASPNETCoreWebAPI.Controllers
{
    public class EmployeeController : ControllerBase
    {
        [HttpGet]
        public string GetAllEmployees()
        {
            return "Response from GetAllEmployees Method";
        }

        [HttpGet]
        public string GetEmployeeById(int Id)
        {
            return $"Response from GetEmployeeById Method Id: {Id}";
        }
    }
}

With the above changes in place, you can access the APIs using /api/Employee/GetAllEmployees and api/Employee/GetEmployeeById/103 URL, as shown in the below image.

How to Implement Conventional-Based Routing in ASP.NET Core Web API

Can we use Both Conventional and Attribute-Based Routing in ASP.NET Core Web API?

Yes, we can use Conventional and Attribute-Based Routing in the same ASP.NET Core Web API Applications. The action method decorated with a Route Attribute will use Attribute Routing, and the action method without the Route Attribute will use Conventional Routing. For a better understanding, please modify the Employee Controller as follows:

using Microsoft.AspNetCore.Mvc;
namespace RoutingInASPNETCoreWebAPI.Controllers
{
    public class EmployeeController : ControllerBase
    {
        [Route("Emp/All")]
        [HttpGet]
        public string GetAllEmployees()
        {
            return "Response from GetAllEmployees Method";
        }

        [HttpGet]
        public string GetEmployeeById(int Id)
        {
            return $"Response from GetEmployeeById Method Id: {Id}";
        }
    }
}

As you can see in the above code, now we can access the GetAllEmployees method using the Attribute Routing, i.e., /Emp/All URL, and we can access the GetEmployeeById action method using the Conventional Routing, i.e., /api/employee/GetEmployeeById/102 URL as shown in the below image:

Can we use Both Conventional and Attribute-Based Routing in ASP.NET Core Web API?

Using Attribute Routing to Perform CRUD Operations in ASP.NET Core Web API:

Let us understand this with an example. The following are the commonly used HTTP Verb Attributes in ASP.NET Core Web API.

  • [HttpGet] – Indicates that an action method responds to an HTTP GET request. It is often used to retrieve data from the server.
  • [HttpPost] – Specifies that an action method responds to an HTTP POST request. This method is typically used to submit data to the server.
  • [HttpPut] – Denotes that an action method handles HTTP PUT requests, commonly used for updating existing resources.
  • [HttpDelete] – Indicates that an action method responds to an HTTP DELETE request for deleting resources.
  • [HttpPatch] – Used for handling HTTP PATCH requests, typically used to update resources partially.
Create the Employee Class:

First, let’s create a model class to represent the Employee data. First, add a Models Folder. To do so, right-click on the project in Solution Explorer and select Add > New Folder. Name it Models. Then, right-click on the Models folder and select Add > Class. Name it Employee.cs and add the following code:

using System.ComponentModel.DataAnnotations;
namespace RoutingInASPNETCoreWebAPI.Models
{
    public class Employee
    {
        public int Id { get; set; }

        [Required]
        [StringLength(100)]
        public string Name { get; set; }

        [StringLength(100)]
        public string Position { get; set; }

        [Range(18, 65)]
        public int Age { get; set; }

        [EmailAddress]
        public string Email { get; set; }
    }
}
Creating the In-Memory Data Store

We will use an in-memory list to store employee data to simulate database operations.

Create the Repository Interface:

Right-click on the project, select Add > New Folder and name it Repositories. Then, add a new interface named IEmployeeRepository.cs within the Repositories folder and then copy and paste the following code:

using RoutingInASPNETCoreWebAPI.Models;
namespace RoutingInASPNETCoreWebAPI.Repositories
{
    public interface IEmployeeRepository
    {
        IEnumerable<Employee> GetAll();
        Employee? GetById(int id);
        void Add(Employee employee);
        void Update(Employee employee);
        void Delete(int id);
        bool Exists(int id);
    }
}
Implement the Repository:

Add a new class named EmployeeRepository.cs in the Repositories folder, and then copy and paste the following code:

using RoutingInASPNETCoreWebAPI.Models;
namespace RoutingInASPNETCoreWebAPI.Repositories
{
    public class EmployeeRepository : IEmployeeRepository
    {
        private readonly List<Employee> _employees;

        public EmployeeRepository()
        {
            // Initialize with some sample data
            _employees = new List<Employee>
            {
                new Employee { Id = 1, Name = "John Doe", Position = "Software Engineer", Age = 30, Email = "john.doe@example.com" },
                new Employee { Id = 2, Name = "Jane Smith", Position = "Project Manager", Age = 35, Email = "jane.smith@example.com" }
            };
        }

        public IEnumerable<Employee> GetAll()
        {
            return _employees;
        }

        public Employee? GetById(int id)
        {
            return _employees.FirstOrDefault(e => e.Id == id);
        }

        public void Add(Employee employee)
        {
            employee.Id = _employees.Max(e => e.Id) + 1;
            _employees.Add(employee);
        }

        public void Update(Employee employee)
        {
            var existingEmployee = GetById(employee.Id);
            if (existingEmployee != null)
            {
                existingEmployee.Name = employee.Name;
                existingEmployee.Position = employee.Position;
                existingEmployee.Age = employee.Age;
                existingEmployee.Email = employee.Email;
            }
        }

        public void Delete(int id)
        {
            var employee = GetById(id);
            if (employee != null)
            {
                _employees.Remove(employee);
            }
        }

        public bool Exists(int id)
        {
            return _employees.Any(e => e.Id == id);
        }
    }
}
Configuring EmployeeRepository with Dependency Injection

To ensure that the EmployeeRepository is available via Dependency Injection (DI), register it in the Program.cs file. Add the following lines to register the IEmployeeRepository with its implementation:

// Register the EmployeeRepository for Dependency Injection
builder.Services.AddSingleton<IEmployeeRepository, EmployeeRepository>();

This registers the EmployeeRepository as a singleton service, meaning a single instance is used throughout the application’s lifetime. This is suitable for an in-memory data store. For database contexts (e.g., Entity Framework), we typically use AddDbContext with a scoped lifetime. This also allows EmployeeController to receive IEmployeeRepository via constructor injection.

Creating the EmployeeController

Let’s modify the EmployeeController to perform CRUD operations using Attribute Routing. So, please modify EmployeeController as follows:

using Microsoft.AspNetCore.Mvc;
using RoutingInASPNETCoreWebAPI.Models;
using RoutingInASPNETCoreWebAPI.Repositories;

namespace RoutingInASPNETCoreWebAPI.Controllers
{
    [ApiController] 
    [Route("api/[controller]")] //sets the base route to api/employee
    public class EmployeeController : ControllerBase
    {
        private readonly IEmployeeRepository _repository;

        //Injects IEmployeeRepository to manage data operations.
        public EmployeeController(IEmployeeRepository repository)
        {
            _repository = repository;
        }

        //  Retrieves all employees (GET api/employee).
        [HttpGet]
        public ActionResult<IEnumerable<Employee>> GetAllEmployees()
        {
            var employees = _repository.GetAll();
            return Ok(employees);
        }

        //Retrieves a specific employee by ID(GET api/employee/{id}).
        [HttpGet("{id}")]
        public ActionResult<Employee> GetEmployeeById(int id)
        {
            var employee = _repository.GetById(id);
            if (employee == null)
            {
                return NotFound();
            }
            return Ok(employee);
        }

        //Adds a new employee(POST api/employee).
        [HttpPost]
        public ActionResult<Employee> CreateEmployee([FromBody] Employee employee)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            _repository.Add(employee);
            return CreatedAtAction(nameof(GetEmployeeById), new { id = employee.Id }, employee);
        }

        // Updates an existing employee entirely (PUT api/employee/{id}).
        [HttpPut("{id}")]
        public IActionResult UpdateEmployee(int id, [FromBody] Employee employee)
        {
            if (id != employee.Id)
            {
                return BadRequest("Employee ID mismatch.");
            }

            if (!_repository.Exists(id))
            {
                return NotFound();
            }

            _repository.Update(employee);
            return NoContent();
        }

        // Partially updates an existing employee (PATCH api/employee/{id}).
        [HttpPatch("{id}")]
        public IActionResult PatchEmployee(int id, [FromBody] Employee employee)
        {
            var existingEmployee = _repository.GetById(id);
            if (existingEmployee == null)
            {
                return NotFound();
            }

            // For simplicity, updating all fields. In real scenarios, use JSON Patch.
            existingEmployee.Name = employee.Name ?? existingEmployee.Name;
            existingEmployee.Position = employee.Position ?? existingEmployee.Position;
            existingEmployee.Age = employee.Age != 0 ? employee.Age : existingEmployee.Age;
            existingEmployee.Email = employee.Email ?? existingEmployee.Email;

            _repository.Update(existingEmployee);
            return NoContent();
        }

        // Deletes an employee (DELETE api/employee/{id}).
        [HttpDelete("{id}")]
        public IActionResult DeleteEmployee(int id)
        {
            if (!_repository.Exists(id))
            {
                return NotFound();
            }

            _repository.Delete(id);
            return NoContent();
        }
    }
}

Now, you can access the above endpoints, which should work as expected.

Differences Between Route Attribute and HTTP Method Attribute:

In ASP.NET Core Web API, both Route Attribute and HTTP Method Attribute are used to apply route patterns to controller actions, but they serve slightly different purposes:

The Route Attribute ([Route]) is used to specify a route template for a controller or an action. The [Route] attribute can be applied at the controller level to define a base route for all action methods within the controller. It can also be applied at the action level to define specific routes for individual actions.

The HTTP method attributes explicitly define which HTTP methods an action method should respond to. These attributes make it clear what kind of HTTP requests (GET, POST, PUT, DELETE, PATCH) an action can handle. HTTP Method Attributes can also define the route template. So, it serves two purposes: Method Type and URL Pattern.

When to Use Convention-Based vs Attribute-Based Routing?

If your application is purely a Web API, it’s common to stick with attribute routing for clarity and explicitness. Convention-based routing is more typical in MVC-style apps rendering Razor views. ASP.NET Core allows hybrid usage of both approaches.

Routing is a fundamental concept in ASP.NET Core Web API that determines how HTTP requests are mapped to controller actions. Effective routing ensures incoming requests are correctly mapped to the appropriate endpoints, enabling our application to respond accurately and efficiently.

By understanding convention-based and attribute-based routing and how the middleware pipeline processes requests, you can design clean, consistent, and flexible APIs. Attribute routing is typically the recommended approach for modern Web API projects, but you can mix and match as needed for your particular scenario.

In the next article, I will discuss Working with Variables and Query Strings in Routing with Examples. In this article, I explain Routing in ASP.NET Core Web API Applications, how the Routing mechanism works, and how to configure Routing in ASP.NET Core Web API Applications with Examples. I hope you enjoy this article on Routing in ASP.NET Core Web API Applications.

8 thoughts on “Routing in ASP.NET Core Web API”

Leave a Reply

Your email address will not be published. Required fields are marked *