Building RESTful APIs with ASP.NET Core

This tutorial guides you through the process of creating robust and scalable RESTful APIs using ASP.NET Core. We'll cover fundamental concepts and best practices to help you build efficient web services.

Introduction

ASP.NET Core provides a powerful and flexible framework for building modern, cloud-based, internet-connected applications, including high-performance RESTful APIs. APIs (Application Programming Interfaces) are the backbone of modern software, enabling different applications to communicate and share data.

In this tutorial, you will learn how to:

Getting Started

Before you begin, ensure you have the following installed:

You can download the .NET SDK from the official Microsoft website.

Creating a Web API Project

Open your terminal or command prompt and run the following command to create a new ASP.NET Core Web API project:

dotnet new webapi -o MyWebApiProject

This command creates a new directory named MyWebApiProject and generates a basic Web API project structure within it.

Routing

Routing is the process of mapping incoming HTTP requests to the appropriate code in your application. ASP.NET Core uses a convention-based routing system by default.

In a typical ASP.NET Core Web API project, routing is configured in Program.cs (or Startup.cs in older versions).

// Program.cs (example)
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllers();

            var app = builder.Build();

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

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.MapControllers(); // This maps controllers to routes

            app.Run();

The app.MapControllers() line enables attribute routing and convention-based routing for controllers.

Controllers

Controllers are classes that handle incoming HTTP requests. They are typically placed in a Controllers folder.

A controller class must inherit from ControllerBase and be decorated with the [ApiController] attribute, which enables several API-specific behaviors.

// Controllers/ProductsController.cs
            using Microsoft.AspNetCore.Mvc;

            namespace MyWebApiProject.Controllers
            {
                [ApiController]
                [Route("api/[controller]")] // Convention-based routing for this controller
                public class ProductsController : ControllerBase
                {
                    // Actions will go here
                }
            }

The [Route("api/[controller]")] attribute defines a base route for all actions within this controller. [controller] is a placeholder that will be replaced by the controller's name (e.g., "Products").

Actions

Actions are public methods within a controller that handle specific HTTP requests. They are mapped to specific HTTP verbs (GET, POST, PUT, DELETE) using attributes like [HttpGet], [HttpPost], etc.

// Controllers/ProductsController.cs
            using Microsoft.AspNetCore.Mvc;
            using MyWebApiProject.Models; // Assuming you have a Product model
            using System.Collections.Generic;
            using System.Linq;

            namespace MyWebApiProject.Controllers
            {
                [ApiController]
                [Route("api/[controller]")]
                public class ProductsController : ControllerBase
                {
                    private static List _products = new List
                    {
                        new Product { Id = 1, Name = "Laptop", Price = 1200.00M },
                        new Product { Id = 2, Name = "Keyboard", Price = 75.50M }
                    };

                    [HttpGet] // Handles GET requests to /api/products
                    public ActionResult> GetProducts()
                    {
                        return Ok(_products);
                    }

                    [HttpGet("{id}")] // Handles GET requests to /api/products/{id}
                    public ActionResult GetProduct(int id)
                    {
                        var product = _products.FirstOrDefault(p => p.Id == id);
                        if (product == null)
                        {
                            return NotFound(); // Returns 404 Not Found
                        }
                        return Ok(product); // Returns 200 OK with the product
                    }

                    [HttpPost] // Handles POST requests to /api/products
                    public ActionResult CreateProduct([FromBody] Product newProduct)
                    {
                        if (!ModelState.IsValid)
                        {
                            return BadRequest(ModelState);
                        }
                        newProduct.Id = _products.Max(p => p.Id) + 1;
                        _products.Add(newProduct);
                        return CreatedAtAction(nameof(GetProduct), new { id = newProduct.Id }, newProduct);
                    }
                }
            }
Visual Studio Code creating a controller Creating a new controller in Visual Studio Code.

Request & Response Handling

ASP.NET Core provides rich support for handling HTTP requests and crafting HTTP responses.

Data Binding

Model binding automatically translates incoming HTTP request data (from route, query string, form, or body) into parameters of your controller actions.

You can explicitly specify the source of the binding using attributes like:

If no attribute is specified, ASP.NET Core attempts to bind from multiple sources.

Model Validation

You can use data annotations (from System.ComponentModel.DataAnnotations) to define validation rules for your models. The [ApiController] attribute automatically performs model validation based on these annotations.

// Models/Product.cs
            using System.ComponentModel.DataAnnotations;

            namespace MyWebApiProject.Models
            {
                public class Product
                {
                    public int Id { get; set; }

                    [Required(ErrorMessage = "Product name is required.")]
                    [StringLength(100, ErrorMessage = "Product name cannot exceed 100 characters.")]
                    public string Name { get; set; }

                    [Range(0.01, double.MaxValue, ErrorMessage = "Price must be a positive value.")]
                    public decimal Price { get; set; }
                }
            }

If validation fails, ModelState.IsValid will be false, and BadRequest(ModelState) will return a 400 response containing validation errors.

Dependency Injection

ASP.NET Core has built-in support for Dependency Injection (DI), making it easy to manage services and dependencies.

Register your services in Program.cs:

// Program.cs
            builder.Services.AddScoped(); // Example registration

Then, inject them into your controllers via the constructor:

// Controllers/ProductsController.cs
            public class ProductsController : ControllerBase
            {
                private readonly IProductService _productService;

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

                [HttpGet]
                public async Task>> GetProducts()
                {
                    var products = await _productService.GetAllProductsAsync();
                    return Ok(products);
                }
                // ... other actions
            }

Error Handling

Implement robust error handling to provide informative responses to clients.

Global Exception Handling: Use middleware in Program.cs to catch unhandled exceptions.

// Program.cs
            app.UseExceptionHandler(appError =>
            {
                appError.Run(async context =>
                {
                    context.Response.StatusCode = StatusCodes.Status500InternalServerError;
                    context.Response.ContentType = "application/json";
                    var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
                    if(contextFeature != null)
                    {
                        // Log the error: _logger.LogError($"Something went wrong: {contextFeature.Error}");
                        await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(new { Message = "An unexpected error occurred." }));
                    }
                });
            });

Specific Exception Handling: Throw specific exceptions within your services and catch them in controllers to return appropriate HTTP status codes.

Authentication & Authorization

Securing your API is crucial. ASP.NET Core supports various authentication and authorization schemes.

Use the [Authorize] attribute on controllers or actions to protect endpoints.

[Authorize] // Requires authentication for all actions in this controller
            public class OrdersController : ControllerBase
            {
                [HttpGet]
                public IActionResult GetOrders() { ... }

                [Authorize(Roles = "Admin")] // Requires authentication and the 'Admin' role
                [HttpDelete("{id}")]
                public IActionResult DeleteOrder(int id) { ... }
            }

Configure authentication services in Program.cs using libraries like Microsoft.AspNetCore.Authentication.JwtBearer.

Tip: Consider using ASP.NET Core Identity for built-in user management and authentication.

Testing APIs

Writing tests is vital for ensuring your API functions correctly.

Use testing frameworks like xUnit or NUnit with ASP.NET Core's built-in test host for integration tests.

Deployment

Once your API is ready, you can deploy it to various environments:

Build your project in Release mode for optimal performance:

dotnet publish -c Release -o ./publish

Congratulations! You have now learned the fundamentals of building RESTful APIs with ASP.NET Core. Continue exploring advanced topics like OpenAPI (Swagger) integration, versioning, and performance optimization.

Back to Tutorials