.NET Web API
Introduction
ASP.NET Core Web API is a powerful framework for building HTTP-based services that can reach a broad range of clients, including browsers, mobile devices, desktop applications, and other microservices. It's a lightweight and highly testable way to create RESTful services using the .NET platform.
In this guide, we'll explore how to build Web APIs with ASP.NET Core, understand the core concepts, and develop practical applications that follow industry best practices.
What is a Web API?
A Web API (Application Programming Interface) is an interface that provides data and functionality via HTTP requests. Unlike traditional web applications that generate HTML for browsers, Web APIs typically return structured data in formats like JSON or XML, making them ideal for:
- Frontend frameworks like React, Angular, or Vue.js
- Mobile applications
- Desktop applications
- Integration with other services
- IoT devices
Getting Started with ASP.NET Core Web API
Prerequisites
Before we begin, ensure you have:
- .NET SDK (version 6.0 or later)
- A code editor (Visual Studio, VS Code with C# extension, JetBrains Rider, etc.)
Creating Your First Web API Project
Let's create a simple Web API project using the .NET CLI:
dotnet new webapi -n MyFirstWebApi
cd MyFirstWebApi
This command creates a new Web API project with a pre-configured controller. The project structure will look something like:
MyFirstWebApi/
├── Controllers/
│ └── WeatherForecastController.cs
├── Properties/
│ └── launchSettings.json
├── appsettings.json
├── appsettings.Development.json
├── Program.cs
├── MyFirstWebApi.csproj
└── WeatherForecast.cs
Understanding the Default Project
The template includes a WeatherForecastController
that returns random weather data. Let's look at its structure:
using Microsoft.AspNetCore.Mvc;
namespace MyFirstWebApi.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
When you run the application and navigate to /weatherforecast
, you'll see JSON output similar to:
[
{
"date": "2023-10-16T10:21:37.388565+01:00",
"temperatureC": 16,
"temperatureF": 60,
"summary": "Balmy"
},
{
"date": "2023-10-17T10:21:37.3885726+01:00",
"temperatureC": -5,
"temperatureF": 24,
"summary": "Chilly"
},
// ... more entries
]
Key Concepts in ASP.NET Core Web API
Controllers
Controllers in Web API are classes that handle HTTP requests and generate responses. They are decorated with attributes that define their behavior:
[ApiController]
: Enables API-specific behaviors like automatic model validation[Route]
: Defines the URL pattern for accessing the controller
HTTP Methods
Web APIs use HTTP methods to define the action to be performed:
GET
: Retrieve dataPOST
: Create new dataPUT
: Update existing data completelyPATCH
: Update existing data partiallyDELETE
: Remove data
Model Binding and Validation
ASP.NET Core automatically binds HTTP request data to method parameters and validates the model according to data annotations.
Building a Complete Web API
Let's build a simple Book Management API to understand the concepts better.
Step 1: Define the Model
Create a folder called Models
and add a Book.cs
file:
namespace MyFirstWebApi.Models;
public class Book
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty;
public int PublicationYear { get; set; }
public string ISBN { get; set; } = string.Empty;
}
Step 2: Create a Repository
Create a folder called Repositories
and add the following files to manage data operations:
First, create an interface IBookRepository.cs
:
using MyFirstWebApi.Models;
namespace MyFirstWebApi.Repositories;
public interface IBookRepository
{
IEnumerable<Book> GetAll();
Book? GetById(int id);
Book Add(Book book);
Book? Update(Book book);
bool Delete(int id);
}
Then, implement the interface with an in-memory repository BookRepository.cs
:
using MyFirstWebApi.Models;
namespace MyFirstWebApi.Repositories;
public class BookRepository : IBookRepository
{
private readonly List<Book> _books = new List<Book>
{
new Book { Id = 1, Title = "Clean Code", Author = "Robert C. Martin", PublicationYear = 2008, ISBN = "978-0132350884" },
new Book { Id = 2, Title = "Design Patterns", Author = "Erich Gamma et al.", PublicationYear = 1994, ISBN = "978-0201633610" },
new Book { Id = 3, Title = "The Pragmatic Programmer", Author = "Andrew Hunt, David Thomas", PublicationYear = 1999, ISBN = "978-0201616224" }
};
public IEnumerable<Book> GetAll()
{
return _books;
}
public Book? GetById(int id)
{
return _books.FirstOrDefault(b => b.Id == id);
}
public Book Add(Book book)
{
// In real applications, you would use a database and handle ID generation differently
book.Id = _books.Max(b => b.Id) + 1;
_books.Add(book);
return book;
}
public Book? Update(Book book)
{
var existingBook = _books.FirstOrDefault(b => b.Id == book.Id);
if (existingBook == null) return null;
existingBook.Title = book.Title;
existingBook.Author = book.Author;
existingBook.PublicationYear = book.PublicationYear;
existingBook.ISBN = book.ISBN;
return existingBook;
}
public bool Delete(int id)
{
var book = _books.FirstOrDefault(b => b.Id == id);
if (book == null) return false;
return _books.Remove(book);
}
}
Step 3: Register the Repository in Program.cs
Add the following code to your Program.cs
file to register the repository:
using MyFirstWebApi.Repositories;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register the repository as a singleton
builder.Services.AddSingleton<IBookRepository, BookRepository>();
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();
Step 4: Create the Controller
Now, let's create a controller to expose endpoints for book operations. Add a BooksController.cs
file in the Controllers folder:
using Microsoft.AspNetCore.Mvc;
using MyFirstWebApi.Models;
using MyFirstWebApi.Repositories;
namespace MyFirstWebApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
private readonly IBookRepository _bookRepository;
private readonly ILogger<BooksController> _logger;
public BooksController(IBookRepository bookRepository, ILogger<BooksController> logger)
{
_bookRepository = bookRepository;
_logger = logger;
}
// GET: api/Books
[HttpGet]
public ActionResult<IEnumerable<Book>> GetBooks()
{
_logger.LogInformation("Getting all books");
return Ok(_bookRepository.GetAll());
}
// GET: api/Books/5
[HttpGet("{id}")]
public ActionResult<Book> GetBook(int id)
{
_logger.LogInformation($"Getting book with ID: {id}");
var book = _bookRepository.GetById(id);
if (book == null)
{
_logger.LogWarning($"Book with ID: {id} not found");
return NotFound();
}
return Ok(book);
}
// POST: api/Books
[HttpPost]
public ActionResult<Book> CreateBook(Book book)
{
_logger.LogInformation($"Creating a new book: {book.Title}");
var newBook = _bookRepository.Add(book);
return CreatedAtAction(nameof(GetBook), new { id = newBook.Id }, newBook);
}
// PUT: api/Books/5
[HttpPut("{id}")]
public IActionResult UpdateBook(int id, Book book)
{
if (id != book.Id)
{
_logger.LogWarning($"ID mismatch: URL ID {id} != Book ID {book.Id}");
return BadRequest();
}
var updatedBook = _bookRepository.Update(book);
if (updatedBook == null)
{
_logger.LogWarning($"Book with ID: {id} not found");
return NotFound();
}
return NoContent();
}
// DELETE: api/Books/5
[HttpDelete("{id}")]
public IActionResult DeleteBook(int id)
{
_logger.LogInformation($"Deleting book with ID: {id}");
var result = _bookRepository.Delete(id);
if (!result)
{
_logger.LogWarning($"Book with ID: {id} not found");
return NotFound();
}
return NoContent();
}
}
Step 5: Test the API
Run your application:
dotnet run
You can now test your API using tools like:
- Swagger UI (automatically available at
/swagger
in development) - Postman
- curl
- Your web browser (for GET requests)
Here's how to test with curl:
Get all books:
curl -X GET https://localhost:7001/api/books
Get a specific book:
curl -X GET https://localhost:7001/api/books/1
Create a new book:
curl -X POST https://localhost:7001/api/books \
-H "Content-Type: application/json" \
-d '{"title":"The Clean Coder","author":"Robert C. Martin","publicationYear":2011,"isbn":"978-0137081073"}'
Update a book:
curl -X PUT https://localhost:7001/api/books/1 \
-H "Content-Type: application/json" \
-d '{"id":1,"title":"Clean Code: A Handbook of Agile Software Craftsmanship","author":"Robert C. Martin","publicationYear":2008,"isbn":"978-0132350884"}'
Delete a book:
curl -X DELETE https://localhost:7001/api/books/1
Advanced Topics
API Versioning
As your API evolves, you may need to maintain backward compatibility. ASP.NET Core supports API versioning:
// In Program.cs
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
// In controllers
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
public class BooksController : ControllerBase
{
// Controller implementation
}
Content Negotiation
Content negotiation allows clients to specify the format of the response:
// In Program.cs
builder.Services.AddControllers()
.AddXmlSerializerFormatters(); // Adds XML formatting in addition to JSON
Clients can now request XML by setting the Accept header:
curl -X GET https://localhost:7001/api/books -H "Accept: application/xml"
Authentication and Authorization
Secure your Web API using JWT authentication:
// In Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
// Add to request pipeline
app.UseAuthentication();
app.UseAuthorization();
Then, secure your controllers or actions with the [Authorize]
attribute:
[Authorize]
[HttpGet]
public ActionResult<IEnumerable<Book>> GetBooks()
{
// Only authenticated users can access this endpoint
return Ok(_bookRepository.GetAll());
}
Best Practices for Web API Development
-
Use HTTP methods correctly:
- GET for reading
- POST for creating
- PUT for updating
- DELETE for removing
-
Return appropriate HTTP status codes:
- 200 (OK) for successful operations
- 201 (Created) when a resource is created
- 204 (No Content) for successful operations that don't return data
- 400 (Bad Request) for client errors
- 401/403 (Unauthorized/Forbidden) for authentication/authorization issues
- 404 (Not Found) when resources don't exist
- 500 (Internal Server Error) for server errors
-
Use DTOs (Data Transfer Objects) to separate your internal models from the external API contract
-
Implement pagination for endpoints that return collections
-
Use action filters for cross-cutting concerns like logging or validation
-
Document your API using Swagger/OpenAPI
-
Implement proper error handling to provide meaningful error responses
Real-World Example: Implementing API Documentation with Swagger
Swagger (OpenAPI) is already included in the template, but let's enhance it with additional information:
// In Program.cs
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "Books API",
Description = "An API for managing books",
Contact = new OpenApiContact
{
Name = "Your Name",
Email = "[email protected]"
},
License = new OpenApiLicense
{
Name = "MIT License",
Url = new Uri("https://opensource.org/licenses/MIT")
}
});
// Include XML comments if you've configured your project to generate them
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});
To generate XML documentation, add this to your .csproj
file:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
Then, add documentation to your controller:
/// <summary>
/// Gets all books in the library
/// </summary>
/// <returns>A collection of books</returns>
/// <response code="200">Returns the list of books</response>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<Book>> GetBooks()
{
_logger.LogInformation("Getting all books");
return Ok(_bookRepository.GetAll());
}
Summary
In this comprehensive guide, we've explored ASP.NET Core Web API from the ground up:
- We learned what Web APIs are and their importance in modern software development
- We created a new Web API project and understood its structure
- We implemented a complete CRUD API for a book management system
- We covered advanced topics like versioning, content negotiation, and authentication
- We discussed best practices and provided real-world examples
ASP.NET Core Web API provides a robust and flexible platform for building HTTP services. With its strong ecosystem and integration with the broader .NET platform, it's an excellent choice for developing APIs of any scale.
Additional Resources and Exercises
Resources:
- Microsoft's official ASP.NET Core Web API documentation
- RESTful Web API Design Best Practices
- The Richardson Maturity Model for REST API design
Exercises:
-
Extend the Book API to include additional features:
- Search by title or author
- Filter by publication year range
- Pagination for large result sets
-
Implement a Review system where users can post reviews for books:
- Create models, controllers, and repositories for reviews
- Implement a relationship between books and reviews
-
Secure your API:
- Implement JWT authentication
- Add role-based authorization (Admin, User)
- Ensure only authenticated users can create, update, or delete books
-
Implement advanced validation:
- Ensure ISBN follows a valid format
- Validate that publication year is not in the future
- Check for duplicate ISBNs when adding or updating books
By completing these exercises, you'll gain hands-on experience with ASP.NET Core Web API and be well-prepared to build robust and secure APIs for real-world applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)