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:
- Set up an ASP.NET Core Web API project.
- Define API endpoints using controllers and actions.
- Handle HTTP requests and responses.
- Implement data binding and model validation.
- Utilize dependency injection for cleaner code.
- Secure your API with authentication and authorization.
Getting Started
Before you begin, ensure you have the following installed:
- .NET SDK (latest LTS version recommended)
- A code editor (Visual Studio, VS Code, or Rider)
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);
}
}
}

Request & Response Handling
ASP.NET Core provides rich support for handling HTTP requests and crafting HTTP responses.
- Request Body: Use the
[FromBody]
attribute to bind request data from the body of the request (e.g., JSON for POST/PUT). - Route Parameters: Use route parameters (e.g.,
{id}
) and bind them to action parameters. - Query String Parameters: Use the
[FromQuery]
attribute or let model binding handle them automatically if the parameter name matches. - Response Types:
Ok(data)
: Returns a 200 OK response with the specified data.NotFound()
: Returns a 404 Not Found response.BadRequest()
: Returns a 400 Bad Request response.CreatedAtAction(actionName, routeValues, value)
: Returns a 201 Created response, typically after a POST operation.StatusCode(statusCode, value)
: Allows returning a specific HTTP status code.
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:
[FromRoute]
[FromQuery]
[FromBody]
[FromForm]
[FromHeader]
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.
- Authentication: Verifies the identity of the user or client making the request (e.g., JWT, OAuth, API Keys).
- Authorization: Determines if an authenticated user or client has permission to perform a specific action.
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.
- Unit Tests: Test individual components (e.g., services, models) in isolation.
- Integration Tests: Test the interaction between different components and the ASP.NET Core pipeline.
- End-to-End (E2E) Tests: Test the entire application flow, often simulating client interactions.
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:
- Cloud Platforms: Azure App Service, AWS Elastic Beanstalk, Google App Engine.
- Containers: Docker and Kubernetes.
- On-premises servers.
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