Introduction: REST API in C# — It's Simple
REST API (Representational State Transfer) is an architectural style that allows clients and servers to exchange data via HTTP. In modern web development, API is the de facto standard for creating backends for mobile applications, websites, and microservices. On the .NET platform (C#), REST API development is highly optimized: you can get a working prototype literally in half an hour, even if you are a beginner.
In this article, we will create a full-fledged REST API for managing a ToDo List. You will learn how to set up an ASP.NET Core Web API project, create a data model, implement CRUD operations (Create, Read, Update, Delete), and test the API via Swagger. The entire process will take no more than 30 minutes.
1. Creating the Project and Installing Dependencies
First, open Visual Studio 2022 (or Visual Studio Code with .NET SDK 6.0/7.0/8.0 installed). Follow these steps:
- Select Create a new project → ASP.NET Core Web API.
- Name the project, for example
TodoApi. - Make sure the framework .NET 8.0 (or higher) is selected.
- Uncheck Use controllers if you want to use minimal API, but in this tutorial we will use classic controllers — leave it checked.
- Click Create.
After creating the project, the structure will look like this:
TodoApi/├── Controllers/├── Models/├── Program.cs└── appsettings.jsonNo additional packages need to be installed — all necessary components are already built into ASP.NET Core.
2. Creating the Data Model and Database Context
2.1. TodoItem Model
Create a Models folder and add the TodoItem.cs class to it:
namespace TodoApi.Models;
public class TodoItem{ public int Id { get; set; } public string? Title { get; set; } public bool IsComplete { get; set; }}This is a minimal model: an identifier, a task title, and a completion flag.
2.2. Database Context
Now let's create a context for Entity Framework Core. In the Data folder (create it), add the TodoContext.cs class:
using Microsoft.EntityFrameworkCore;using TodoApi.Models;
namespace TodoApi.Data;
public class TodoContext : DbContext{ public TodoContext(DbContextOptions<TodoContext> options) : base(options) { }
public DbSet<TodoItem> TodoItems { get; set; }}2.3. Configuring Program.cs
Open Program.cs and add the context registration and Swagger:
using Microsoft.EntityFrameworkCore;using TodoApi.Data;
var builder = WebApplication.CreateBuilder(args);
// Add controllersbuilder.Services.AddControllers();
// Register the DB context (using InMemory for simplicity)builder.Services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
// Swaggerbuilder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment()){ app.UseSwagger(); app.UseSwaggerUI();}
app.UseHttpsRedirection();app.UseAuthorization();app.MapControllers();
app.Run();Note: we are using UseInMemoryDatabase — this is convenient for prototyping. In a real project, replace it with SQL Server, PostgreSQL, or another database.
3. Implementing the Controller with CRUD Operations
Now for the most important part — the controller. Create a file TodoItemsController.cs in the Controllers folder:
using Microsoft.AspNetCore.Mvc;using Microsoft.EntityFrameworkCore;using TodoApi.Data;using TodoApi.Models;
namespace TodoApi.Controllers;
[Route("api/[controller]")][ApiController]public class TodoItemsController : ControllerBase{ private readonly TodoContext _context;
public TodoItemsController(TodoContext context) { _context = context; }
// GET: api/TodoItems [HttpGet] public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems() { return await _context.TodoItems.ToListAsync(); }
// GET: api/TodoItems/5 [HttpGet("{id}")] public async Task<ActionResult<TodoItem>> GetTodoItem(int id) { var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null) { return NotFound(); }
return todoItem; }
// POST: api/TodoItems [HttpPost] public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem) { _context.TodoItems.Add(todoItem); await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem); }
// PUT: api/TodoItems/5 [HttpPut("{id}")] public async Task<IActionResult> PutTodoItem(int id, TodoItem todoItem) { if (id != todoItem.Id) { return BadRequest(); }
_context.Entry(todoItem).State = EntityState.Modified;
try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!TodoItemExists(id)) { return NotFound(); } else { throw; } }
return NoContent(); }
// DELETE: api/TodoItems/5 [HttpDelete("{id}")] public async Task<IActionResult> DeleteTodoItem(int id) { var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { return NotFound(); }
_context.TodoItems.Remove(todoItem); await _context.SaveChangesAsync();
return NoContent(); }
private bool TodoItemExists(int id) { return _context.TodoItems.Any(e => e.Id == id); }}