ASP.NET Core Todos Minimal Web API

Find project in https://github.com/csharp-projects-kenanhancer/aspnet-core-todo-minimal-web-api/tree/main

Creating ASP.NET Core Web API Project

$ dotnet new web --name todo-web-api
todo-web-api/
├── Program.cs
├── Properties
│   └── launchSettings.json
├── appsettings.Development.json
├── appsettings.json
├── bin
│   └── Debug
│       └── net7.0
├── obj
│   ├── Debug
│   │   └── net7.0
│   ├── project.assets.json
│   ├── project.nuget.cache
│   ├── todo-web-api.csproj.nuget.dgspec.json
│   ├── todo-web-api.csproj.nuget.g.props
│   └── todo-web-api.csproj.nuget.g.targets
└── todo-web-api.csproj

7 directories, 10 files
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <RootNamespace>todo_web_api</RootNamespace>
  </PropertyGroup>

</Project>
$ code todo-web-api

Adding some endpoints

$ dotnet add package Swashbuckle.AspNetCore
$ dotnet add package Microsoft.AspNetCore.OpenApi
$ dotnet add package Microsoft.EntityFrameworkCore.InMemory
$ dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

// The call to AddEndpointsApiExplorer shown is required only for minimal APIs.
builder.Services.AddEndpointsApiExplorer();

// Register the Swagger generator, defining 1 or more Swagger documents
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Version = "v1",
        Title = "Todo API",
        Description = "An ASP.NET Core Web API for managing todos",
        TermsOfService = new Uri("https://example.com/terms"),
        Contact = new OpenApiContact
        {
            Name = "Kenan Hancer",
            Email = "kenanhancer@gmail.com",
            Url = new Uri("https://kenanhancer.com")
        },
        License = new OpenApiLicense
        {
            Name = "Todo License",
            Url = new Uri("https://example.com/license")
        }
    });
});

builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();


// Configure the HTTP request pipeline:
if (app.Environment.IsDevelopment())
{
    // Enable middleware to serve generated Swagger as a JSON endpoint.
    app.UseSwagger();

    // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
    app.UseSwaggerUI(options =>
    {
        // To serve the Swagger UI at the app's root (https://localhost:<port>/), set the RoutePrefix property to an empty string:
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
        options.RoutePrefix = string.Empty;
    });

    // Seed database
    using (var scope = app.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var database = scopedServices.GetRequiredService<TodoDb>();

        // Ensure the database is created (only applicable for non InMemory providers)
        // database.Database.EnsureCreated();

        List<Todo> todos = new List<Todo>()
        {
            new Todo { Id = Guid.Parse("11111111-1111-1111-1111-111111111111"), Name = "Buy groceries", IsComplete = false },
            new Todo { Id = Guid.Parse("22222222-2222-2222-2222-222222222222"), Name = "Finish project report", IsComplete = true },
            new Todo { Id = Guid.Parse("33333333-3333-3333-3333-333333333333"), Name = "Book doctor appointment", IsComplete = false },
            new Todo { Id = Guid.Parse("44444444-4444-4444-4444-444444444444"), Name = "Walk the dog", IsComplete = true },
            new Todo { Id = Guid.Parse("55555555-5555-5555-5555-555555555555"), Name = "Prepare for meeting", IsComplete = false },
            new Todo { Id = Guid.Parse("66666666-6666-6666-6666-666666666666"), Name = "Pick up kids from school", IsComplete = true },
            new Todo { Id = Guid.Parse("77777777-7777-7777-7777-777777777777"), Name = "Call the bank", IsComplete = false },
            new Todo { Id = Guid.Parse("88888888-8888-8888-8888-888888888888"), Name = "Study for the exam", IsComplete = true },
            new Todo { Id = Guid.Parse("99999999-9999-9999-9999-999999999999"), Name = "Clean the house", IsComplete = false },
            new Todo { Id = Guid.Parse("00000000-0000-0000-0000-000000000000"), Name = "Pay the bills", IsComplete = true }
        };

        database.Todos.AddRange(todos);

        database.SaveChanges();
    }
}

// Endpoints:

app.MapGet("/todos", async (TodoDb db) => { return await db.Todos.ToListAsync(); })
    .WithName("ListTodos")
    .WithDescription("Fetches the todo list")
    .WithSummary("Fetches the todo list")
    .WithTags("Get Operations")
    .WithOpenApi();

app.MapGet("/todos/completed", async (TodoDb db) => { return await db.Todos.Where(t => t.IsComplete).ToListAsync(); })
    .WithName("ListCompletedTodos")
    .WithDescription("Fetches the completed todo list")
    .WithSummary("Fetches the completed todo list")
    .WithTags("Get Operations")
    .WithOpenApi();

app.MapGet("/todos/incomplete", async (TodoDb db) => { return await db.Todos.Where(t => !t.IsComplete).ToListAsync(); })
    .WithName("ListIncompleteTodos")
    .WithDescription("Fetches the todo list")
    .WithSummary("Fetches the todo list")
    .WithTags("Get Operations")
    .WithOpenApi();

// endpoint open API definition can be done in WithOpenApi function as well.
app.MapGet("/todos/{id:guid}",
        async ([FromRoute] Guid id, TodoDb db) =>
        {
            return await db.Todos.FindAsync(id) is Todo todo ? Results.Ok(todo) : Results.NotFound();
        })
    .WithOpenApi(operation =>
        new OpenApiOperation(operation)
        {
            OperationId = "GetTodoById",
            Description = "Get todo by id",
            Summary = "Get todo by id",
            Tags = new List<OpenApiTag>() { new OpenApiTag() { Name = "Get Operations" } },
            Parameters = new List<OpenApiParameter>()
            {
                new OpenApiParameter(operation.Parameters[0])
                {
                    Description = "The ID associated with the created Todo"
                }
            }
        }
    );

app.MapPost("/todos", async ([FromBody] Todo todo, TodoDb db) =>
    {
        db.Todos.Add(todo);
        await db.SaveChangesAsync();

        return Results.Created($"/todos/{todo.Id}", todo);
    })
    .WithName("AddTodoItem")
    .WithDescription("Add todo item")
    .WithSummary("Add todo item")
    .WithTags("Set Operations")
    .WithOpenApi();

app.MapPut("/todos/{id:guid}", async ([FromRoute] Guid id, [FromBody] Todo inputTodo, TodoDb db) =>
    {
        var todo = await db.Todos.FindAsync(id);

        if (todo is null) return Results.NotFound();

        todo.Name = inputTodo.Name;
        todo.IsComplete = inputTodo.IsComplete;

        await db.SaveChangesAsync();

        return Results.NoContent();
    })
    .WithName("ReplaceTodoItem")
    .WithDescription("Replace Todo Item")
    .WithSummary("Replace Todo Item")
    .WithTags("Set Operations")
    .WithOpenApi();

app.MapDelete("/todos/{id:guid}", async ([FromRoute] Guid id, TodoDb db) =>
    {
        if (await db.Todos.FindAsync(id) is Todo todo)
        {
            db.Todos.Remove(todo);
            await db.SaveChangesAsync();

            return Results.Ok(todo);
        }

        return Results.NotFound();
    })
    .WithName("DeleteTodoItemById")
    .WithDescription("Delete Todo Item by Id")
    .WithSummary("Delete Todo Item by Id")
    .WithTags("Set Operations")
    .WithOpenApi();

app.Run();

class Todo
{
    public Guid Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options) : base(options)
    {
    }

    public DbSet<Todo> Todos => Set<Todo>();
}

Debugging

click CMD+SHIFT+P and type .NET: Generate Assets for Build and Debug to add debug config in project(.vscode folder will be generated automatically)

click F5 to start debugger.

I sent request from Swagger UI to /todoitems POST endpoint so debugger stopped in line 29

Swagger UI will be hosted in https://localhost:7235/swagger/ and http://localhost:5164/swagger. Port number can be different because it is dynamic, check your debug terminal as shown below.