C# tutorials > Frameworks and Libraries > ASP.NET Core > Building RESTful APIs with ASP.NET Core Web API (controllers, routing, response codes)

Building RESTful APIs with ASP.NET Core Web API (controllers, routing, response codes)

This tutorial provides a comprehensive guide on building RESTful APIs using ASP.NET Core Web API. It covers creating controllers, defining routes, and managing HTTP response codes effectively.

Setting up a new ASP.NET Core Web API project

To begin, create a new ASP.NET Core Web API project using the .NET CLI. Open your terminal or command prompt and run the command above. This command scaffolds a basic Web API project with pre-configured files and dependencies.

dotnet new webapi -n MyWebApi

Creating a Controller

This code defines a `ProductsController` which inherits from `ControllerBase`. The `[ApiController]` attribute indicates that this is a Web API controller. The `[Route("[controller]")]` attribute defines the route template. The `Get`, `Get(int id)`, `Post`, `Put`, and `Delete` methods define different API endpoints, handling GET, POST, PUT, and DELETE requests respectively. The IActionResult return type enables returning different HTTP status codes (e.g., Ok, NotFound, CreatedAtAction).

// Controllers/ProductsController.cs
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;

namespace MyWebApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class ProductsController : ControllerBase
    {
        private static readonly List<Product> _products = new List<Product>()
        {
            new Product { Id = 1, Name = "Product 1", Price = 10.00M },
            new Product { Id = 2, Name = "Product 2", Price = 20.00M }
        };

        [HttpGet]
        public ActionResult<IEnumerable<Product>> Get()
        {
            return _products;
        }

        [HttpGet("{id}")]
        public ActionResult<Product> Get(int id)
        {
            var product = _products.FirstOrDefault(p => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return product;
        }

        [HttpPost]
        public ActionResult<Product> Post([FromBody] Product newProduct)
        {
          if (newProduct == null) {
            return BadRequest("Product cannot be null");
          }

          newProduct.Id = _products.Count > 0 ? _products.Max(p => p.Id) + 1 : 1;
          _products.Add(newProduct);

          return CreatedAtAction(nameof(Get), new { id = newProduct.Id }, newProduct);
        }

        [HttpPut("{id}")]
        public IActionResult Put(int id, [FromBody] Product updatedProduct)
        {
            if (updatedProduct == null || id != updatedProduct.Id) {
              return BadRequest("Invalid product data");
            }

            var existingProduct = _products.FirstOrDefault(p => p.Id == id);
            if (existingProduct == null) {
                return NotFound();
            }

            existingProduct.Name = updatedProduct.Name;
            existingProduct.Price = updatedProduct.Price;

            return NoContent(); // 204 No Content - successful update
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            var product = _products.FirstOrDefault(p => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }

            _products.Remove(product);
            return NoContent();
        }

    }

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

Understanding Routing

Routing is the process of mapping incoming HTTP requests to the appropriate controller action. ASP.NET Core Web API uses attribute routing, allowing you to define routes directly on your controller actions using attributes like `[HttpGet]`, `[HttpPost]`, `[HttpPut]`, `[HttpDelete]`, and `[Route]`. Route parameters (e.g., `{id}`) are extracted from the URL and passed as arguments to the action method.

Response Codes

HTTP response codes are crucial for communicating the status of API requests to clients. Here's a breakdown of commonly used response codes: * **200 OK:** The request was successful. * **201 Created:** A new resource was successfully created. * **204 No Content:** The request was successful, but there is no content to return. * **400 Bad Request:** The request was invalid. * **401 Unauthorized:** The client is not authenticated. * **403 Forbidden:** The client does not have permission to access the resource. * **404 Not Found:** The resource could not be found. * **500 Internal Server Error:** An unexpected error occurred on the server.

Returning Appropriate Response Codes

Use `IActionResult` to return different HTTP status codes and payloads. For example: * `Ok(result)`: Returns a 200 OK response with the result. * `CreatedAtAction(actionName, routeValues, value)`: Returns a 201 Created response with the location of the newly created resource. * `NotFound()`: Returns a 404 Not Found response. * `BadRequest(errorMessage)`: Returns a 400 Bad Request response with an error message. * `NoContent()`: Returns a 204 No Content response.

Real-Life Use Case

Consider an e-commerce application. The ProductsController manages products, allowing clients to retrieve product details, create new products, update existing products, and delete products. The API endpoints would handle requests from the client-side application (e.g., a React or Angular application) to perform these operations.

Best Practices

  • Use HTTP status codes correctly: Return appropriate status codes to indicate the success or failure of a request.
  • Implement proper validation: Validate incoming data to prevent errors and security vulnerabilities. Use data annotation or fluent validation.
  • Handle exceptions gracefully: Catch exceptions and return meaningful error responses to the client. Use middleware to manage global exception handling.
  • Use asynchronous operations: Use `async` and `await` to avoid blocking the main thread and improve performance.
  • Secure your API: Implement authentication and authorization to protect your API endpoints. Use JWT or OAuth.
  • Versioning: Consider adding API versioning to manage changes and maintain backward compatibility.

Interview Tip

Be prepared to discuss the different HTTP methods (GET, POST, PUT, DELETE) and their corresponding actions (retrieve, create, update, delete). Also, be able to explain the importance of RESTful principles and how ASP.NET Core Web API helps implement them. Know the difference between `IActionResult` and `ActionResult`.

When to use them

Use ASP.NET Core Web API when you need to build a service that exposes data and functionality over HTTP. It's well-suited for building APIs for web applications, mobile apps, and other services. Choose it when you need a fast, scalable, and cross-platform solution.

Alternatives

  • Minimal APIs: Simpler approach in ASP.NET Core for building small APIs with less boilerplate.
  • gRPC: High-performance, contract-based RPC framework, suitable for internal services and microservices architecture.
  • GraphQL: Query language for your API, enabling clients to request specific data, avoiding over-fetching.

Pros

  • Cross-platform: Runs on Windows, macOS, and Linux.
  • High Performance: Optimized for speed and scalability.
  • Built-in Dependency Injection: Simplifies dependency management.
  • Middleware Pipeline: Flexible request processing.
  • Strong Tooling: Excellent tooling support with Visual Studio and .NET CLI.

Cons

  • Learning Curve: Requires understanding of ASP.NET Core concepts and .NET in general.
  • Configuration Overhead: Can be complex to configure, especially for large projects.
  • Steep requirements for microservices : Can be overkill for very simple APIs, Minimal APIs might be a better fit.

FAQ

  • What is the difference between `IActionResult` and `ActionResult`?

    `IActionResult` allows you to return any type of HTTP response (e.g., Ok, NotFound, BadRequest). `ActionResult` allows you to return a specific type `T` or an `IActionResult`. `ActionResult` provides compile-time checking and helps prevent returning unexpected data types.
  • How do I handle errors globally in ASP.NET Core Web API?

    Use middleware to catch exceptions and return standardized error responses. Create a custom exception handler middleware that intercepts unhandled exceptions and logs them appropriately. It can then return a JSON response with a consistent error format and appropriate HTTP status code (e.g., 500 Internal Server Error).