Skip to main content

.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:

bash
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:

csharp
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:

json
[
{
"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 data
  • POST: Create new data
  • PUT: Update existing data completely
  • PATCH: Update existing data partially
  • DELETE: 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:

csharp
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:

csharp
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:

csharp
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:

csharp
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:

csharp
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:

bash
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:

bash
curl -X GET https://localhost:7001/api/books

Get a specific book:

bash
curl -X GET https://localhost:7001/api/books/1

Create a new book:

bash
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:

bash
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:

bash
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:

csharp
// 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:

csharp
// In Program.cs
builder.Services.AddControllers()
.AddXmlSerializerFormatters(); // Adds XML formatting in addition to JSON

Clients can now request XML by setting the Accept header:

bash
curl -X GET https://localhost:7001/api/books -H "Accept: application/xml"

Authentication and Authorization

Secure your Web API using JWT authentication:

csharp
// 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:

csharp
[Authorize]
[HttpGet]
public ActionResult<IEnumerable<Book>> GetBooks()
{
// Only authenticated users can access this endpoint
return Ok(_bookRepository.GetAll());
}

Best Practices for Web API Development

  1. Use HTTP methods correctly:

    • GET for reading
    • POST for creating
    • PUT for updating
    • DELETE for removing
  2. 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
  3. Use DTOs (Data Transfer Objects) to separate your internal models from the external API contract

  4. Implement pagination for endpoints that return collections

  5. Use action filters for cross-cutting concerns like logging or validation

  6. Document your API using Swagger/OpenAPI

  7. 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:

csharp
// 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:

xml
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>

Then, add documentation to your controller:

csharp
/// <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:

  1. We learned what Web APIs are and their importance in modern software development
  2. We created a new Web API project and understood its structure
  3. We implemented a complete CRUD API for a book management system
  4. We covered advanced topics like versioning, content negotiation, and authentication
  5. 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:

Exercises:

  1. Extend the Book API to include additional features:

    • Search by title or author
    • Filter by publication year range
    • Pagination for large result sets
  2. 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
  3. Secure your API:

    • Implement JWT authentication
    • Add role-based authorization (Admin, User)
    • Ensure only authenticated users can create, update, or delete books
  4. 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! :)