C# Web API
Introduction
Web APIs (Application Programming Interfaces) are a crucial component in modern web development, serving as the bridge between your server-side data and client-side applications. In C#, building Web APIs has become streamlined with the ASP.NET Core framework, making it accessible even for beginners.
In this tutorial, you'll learn how to create, configure, and use Web APIs using C# and ASP.NET Core. You'll understand how to expose data endpoints that can be consumed by web applications, mobile apps, or other services, following RESTful principles.
What is a Web API?
A Web API is a set of HTTP endpoints that allow applications to interact with your server and data. Unlike traditional websites that return HTML for browser rendering, Web APIs typically return data in formats like JSON or XML, making them perfect for:
- Building Single Page Applications (SPAs)
- Powering mobile applications
- Enabling service-to-service communication
- Creating microservices architectures
Setting Up Your First Web API Project
Prerequisites
- .NET SDK installed
- A code editor (Visual Studio, VS Code, etc.)
- Basic knowledge of C# programming
Creating a New Web API Project
Open your terminal or command prompt and run:
dotnet new webapi -n MyFirstWebApi
cd MyFirstWebApi
This creates a new Web API project with a sample controller. Let's explore the project structure:
Key Project Components
- Controllers/: Contains your API endpoints
- Program.cs: Application entry point and configuration
- appsettings.json: Configuration settings
Understanding Controllers and Endpoints
Controllers are classes that handle HTTP requests. Here's a basic example of a weather forecast controller (included in the template):
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 = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
Let's break down this code:
[ApiController]
: Enables API-specific behavior[Route("[controller]")]
: Routes requests based on the controller nameHttpGet
: Specifies that this method handles HTTP GET requestsIEnumerable<WeatherForecast>
: The return type, which will be converted to JSON
To run your API:
dotnet run
Your API will be accessible at https://localhost:5001/weatherforecast
(or http://localhost:5000/weatherforecast
).
Building a Custom API - Product Catalog
Let's create a more realistic example: a simple product catalog API with CRUD operations (Create, Read, Update, Delete).
Step 1: Create a Product Model
Create a new file named Product.cs
in a new Models
folder:
namespace MyFirstWebApi.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Price { get; set; }
public int StockQuantity { get; set; }
}
}
Step 2: Create a Product Service
Create a new folder named Services
and add a file named ProductService.cs
:
using MyFirstWebApi.Models;
namespace MyFirstWebApi.Services
{
public interface IProductService
{
IEnumerable<Product> GetAll();
Product? GetById(int id);
Product Create(Product product);
bool Update(Product product);
bool Delete(int id);
}
public class ProductService : IProductService
{
// In-memory product list (in a real app, this would use a database)
private readonly List<Product> _products = new()
{
new Product { Id = 1, Name = "Laptop", Description = "High-performance laptop", Price = 1299.99m, StockQuantity = 10 },
new Product { Id = 2, Name = "Smartphone", Description = "Latest model smartphone", Price = 899.99m, StockQuantity = 15 },
new Product { Id = 3, Name = "Headphones", Description = "Noise-cancelling headphones", Price = 249.99m, StockQuantity = 20 }
};
public IEnumerable<Product> GetAll() => _products;
public Product? GetById(int id) => _products.FirstOrDefault(p => p.Id == id);
public Product Create(Product product)
{
// Auto-increment ID (in a real app, the database would handle this)
int newId = _products.Max(p => p.Id) + 1;
product.Id = newId;
_products.Add(product);
return product;
}
public bool Update(Product product)
{
int index = _products.FindIndex(p => p.Id == product.Id);
if (index == -1)
return false;
_products[index] = product;
return true;
}
public bool Delete(int id)
{
int index = _products.FindIndex(p => p.Id == id);
if (index == -1)
return false;
_products.RemoveAt(index);
return true;
}
}
}
Step 3: Register the Service in Program.cs
Add the following line to your Program.cs
file before var app = builder.Build();
:
builder.Services.AddSingleton<IProductService, ProductService>();
Step 4: Create a Products Controller
Create a new file named ProductsController.cs
in the Controllers
folder:
using Microsoft.AspNetCore.Mvc;
using MyFirstWebApi.Models;
using MyFirstWebApi.Services;
namespace MyFirstWebApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
private readonly ILogger<ProductsController> _logger;
public ProductsController(IProductService productService, ILogger<ProductsController> logger)
{
_productService = productService;
_logger = logger;
}
// GET: api/products
[HttpGet]
public IActionResult GetAll()
{
var products = _productService.GetAll();
return Ok(products);
}
// GET: api/products/5
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
var product = _productService.GetById(id);
if (product == null)
return NotFound();
return Ok(product);
}
// POST: api/products
[HttpPost]
public IActionResult Create(Product product)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var createdProduct = _productService.Create(product);
return CreatedAtAction(nameof(GetById), new { id = createdProduct.Id }, createdProduct);
}
// PUT: api/products/5
[HttpPut("{id}")]
public IActionResult Update(int id, Product product)
{
if (id != product.Id)
return BadRequest("ID mismatch");
if (!ModelState.IsValid)
return BadRequest(ModelState);
bool success = _productService.Update(product);
if (!success)
return NotFound();
return NoContent();
}
// DELETE: api/products/5
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
bool success = _productService.Delete(id);
if (!success)
return NotFound();
return NoContent();
}
}
}
Step 5: Run and Test Your API
Run your application:
dotnet run
Now you can test your API using a tool like Postman or using curl
commands.
Example Requests and Responses
GET all products:
GET http://localhost:5000/api/products
Response:
[
{
"id": 1,
"name": "Laptop",
"description": "High-performance laptop",
"price": 1299.99,
"stockQuantity": 10
},
{
"id": 2,
"name": "Smartphone",
"description": "Latest model smartphone",
"price": 899.99,
"stockQuantity": 15
},
{
"id": 3,
"name": "Headphones",
"description": "Noise-cancelling headphones",
"price": 249.99,
"stockQuantity": 20
}
]
GET a single product:
GET http://localhost:5000/api/products/1
Response:
{
"id": 1,
"name": "Laptop",
"description": "High-performance laptop",
"price": 1299.99,
"stockQuantity": 10
}
POST a new product:
POST http://localhost:5000/api/products
Content-Type: application/json
{
"name": "Monitor",
"description": "4K Ultra HD Monitor",
"price": 399.99,
"stockQuantity": 5
}
Response:
{
"id": 4,
"name": "Monitor",
"description": "4K Ultra HD Monitor",
"price": 399.99,
"stockQuantity": 5
}
Understanding HTTP Status Codes and Responses
An important part of Web API design is using appropriate HTTP status codes:
- 200 OK: Request succeeded
- 201 Created: Resource successfully created
- 204 No Content: Request succeeded but no content to return
- 400 Bad Request: The server couldn't understand the request
- 404 Not Found: The requested resource doesn't exist
- 500 Internal Server Error: Server-side error
In our ProductsController, we use methods like Ok()
, NotFound()
, and BadRequest()
that automatically map to these status codes.
Adding Input Validation
ASP.NET Core provides built-in model validation. Let's enhance our Product model:
using System.ComponentModel.DataAnnotations;
namespace MyFirstWebApi.Models
{
public class Product
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;
[StringLength(500)]
public string Description { get; set; } = string.Empty;
[Range(0.01, 10000)]
public decimal Price { get; set; }
[Range(0, 1000)]
public int StockQuantity { get; set; }
}
}
With these attributes, the framework will automatically validate incoming requests.
Adding Swagger Documentation
Swagger (OpenAPI) provides interactive documentation for your API. It's already included in the template, but let's customize it:
In Program.cs, update the Swagger configuration:
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() {
Title = "My Product Catalog API",
Version = "v1",
Description = "A simple API to manage product catalog"
});
});
When you run your application, visit https://localhost:5001/swagger
to see the interactive documentation.
Best Practices for API Design
-
Use Proper HTTP Methods:
- GET for retrieving data
- POST for creating new resources
- PUT for updating resources
- DELETE for removing resources
-
Use Appropriate Status Codes: Return meaningful HTTP status codes.
-
Versioning: Consider adding API versioning:
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
-
Rate Limiting: Consider adding rate limiting for production APIs.
-
Security: Use authentication and authorization.
Adding Authentication to Your API
For a basic JWT (JSON Web Token) authentication setup, first, add the required package:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Then, configure authentication in Program.cs
:
// JWT Configuration
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"]))
};
});
builder.Services.AddAuthorization();
// In app configuration section
app.UseAuthentication();
app.UseAuthorization();
Add JWT settings to appsettings.json
:
{
"Jwt": {
"Key": "YourSuperSecretKeyHere",
"Issuer": "YourAppName",
"Audience": "YourAppUsers"
}
}
Now you can protect endpoints with the [Authorize]
attribute:
[HttpGet]
[Authorize]
public IActionResult GetSecretData()
{
return Ok("This is protected data!");
}
Handling Errors and Exceptions
Create a custom middleware for global exception handling:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ErrorHandlingMiddleware> _logger;
public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "An unhandled exception occurred");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return context.Response.WriteAsync(new
{
StatusCode = context.Response.StatusCode,
Message = "An error occurred while processing your request."
}.ToString());
}
}
// Extension method used to add the middleware to the HTTP request pipeline
public static class ErrorHandlingMiddlewareExtensions
{
public static IApplicationBuilder UseErrorHandling(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<ErrorHandlingMiddleware>();
}
}
Add it to your pipeline in Program.cs
:
app.UseErrorHandling();
Summary
In this tutorial, you've learned:
- How to create a Web API using C# and ASP.NET Core
- Creating models, controllers, and services
- Implementing CRUD operations
- Validating input data
- Documenting your API with Swagger
- Securing your API with authentication
- Handling errors gracefully
Web APIs are fundamental to modern web development, allowing you to create powerful backend services that can be consumed by multiple clients.
Additional Resources
- Official ASP.NET Core Web API Documentation
- REST API Design Best Practices
- JWT Authentication in ASP.NET Core
Exercises
- Add pagination to the
GetAll()
method in the ProductsController - Implement searching and filtering capabilities (e.g., search products by name)
- Create a new Category model and controller with a relationship to Products
- Add caching to improve performance of frequently accessed endpoints
- Implement a simple user authentication system with JWT tokens
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)