Skip to main content

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:

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

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 = 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 name
  • HttpGet: Specifies that this method handles HTTP GET requests
  • IEnumerable<WeatherForecast>: The return type, which will be converted to JSON

To run your API:

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

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

csharp
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();:

csharp
builder.Services.AddSingleton<IProductService, ProductService>();

Step 4: Create a Products Controller

Create a new file named ProductsController.cs in the Controllers folder:

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

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

json
[
{
"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:

json
{
"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:

json
{
"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:

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

csharp
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

  1. Use Proper HTTP Methods:

    • GET for retrieving data
    • POST for creating new resources
    • PUT for updating resources
    • DELETE for removing resources
  2. Use Appropriate Status Codes: Return meaningful HTTP status codes.

  3. Versioning: Consider adding API versioning:

csharp
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
  1. Rate Limiting: Consider adding rate limiting for production APIs.

  2. Security: Use authentication and authorization.

Adding Authentication to Your API

For a basic JWT (JSON Web Token) authentication setup, first, add the required package:

bash
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Then, configure authentication in Program.cs:

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

json
{
"Jwt": {
"Key": "YourSuperSecretKeyHere",
"Issuer": "YourAppName",
"Audience": "YourAppUsers"
}
}

Now you can protect endpoints with the [Authorize] attribute:

csharp
[HttpGet]
[Authorize]
public IActionResult GetSecretData()
{
return Ok("This is protected data!");
}

Handling Errors and Exceptions

Create a custom middleware for global exception handling:

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

csharp
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

Exercises

  1. Add pagination to the GetAll() method in the ProductsController
  2. Implement searching and filtering capabilities (e.g., search products by name)
  3. Create a new Category model and controller with a relationship to Products
  4. Add caching to improve performance of frequently accessed endpoints
  5. 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! :)