Find project in https://github.com/csharp-projects-kenanhancer/aspnet-core-book-minimal-web-api
Creating ASP.NET Core Web API Project
$ dotnet new list --tag web
These templates matched your input: --tag='web'
Template Name Short Name Language Tags
-------------------------------------------- ------------------ -------- -------------------------------------
ASP.NET Core Empty web [C#],F# Web/Empty
ASP.NET Core gRPC Service grpc [C#] Web/gRPC
ASP.NET Core Web API webapi [C#],F# Web/WebAPI
ASP.NET Core Web App webapp,razor [C#] Web/MVC/Razor Pages
ASP.NET Core Web App (Model-View-Controller) mvc [C#],F# Web/MVC
ASP.NET Core with Angular angular [C#] Web/MVC/SPA
ASP.NET Core with React.js react [C#] Web/MVC/SPA
ASP.NET Core with React.js and Redux reactredux [C#] Web/MVC/SPA
Blazor Server App blazorserver [C#] Web/Blazor
Blazor Server App Empty blazorserver-empty [C#] Web/Blazor/Empty
Blazor WebAssembly App blazorwasm [C#] Web/Blazor/WebAssembly/PWA
Blazor WebAssembly App Empty blazorwasm-empty [C#] Web/Blazor/WebAssembly/PWA/Empty
MVC ViewImports viewimports [C#] Web/ASP.NET
MVC ViewStart viewstart [C#] Web/ASP.NET
Protocol Buffer File proto Web/gRPC
Razor Class Library razorclasslib [C#] Web/Razor/Library/Razor Class Library
Razor Component razorcomponent [C#] Web/ASP.NET
Razor Page page [C#] Web/ASP.NET
Worker Service worker [C#],F# Common/Worker/Web
$ dotnet new web --name book-web-api
This command generates project folder as well. So folder structure will be as below;
book-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
│ ├── book-web-api.csproj.nuget.dgspec.json
│ ├── book-web-api.csproj.nuget.g.props
│ └── book-web-api.csproj.nuget.g.targets
└── book-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>book_web_api</RootNamespace>
</PropertyGroup>
</Project>
$ code book-web-api
C# 9.0 top level statements is used in Program.cs
. There are only four lines of code.
I just added README.md
, LICENSE
, requests.http
, .gitignore
files 🙂
Adding some endpoints
$ dotnet add package Swashbuckle.AspNetCore
$ dotnet add package Microsoft.AspNetCore.OpenApi
using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Models;
using Microsoft.AspNetCore.Http.HttpResults;
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 = "Book API",
Description = "An ASP.NET Core Web API for managing books",
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 = "Book License",
Url = new Uri("https://example.com/license")
}
});
});
builder.Services.AddSingleton<IDateTime, SystemDateTime>();
builder.Services.AddSingleton<List<Book>>((serviceProvider) =>
new List<Book>()
{
new Book { Id = new Guid("00000000-0000-0000-0000-000000000001"), Title = "Introduction to Algorithms", Author = "Thomas H. Cormen" },
new Book { Id = new Guid("00000000-0000-0000-0000-000000000002"), Title = "Design Patterns: Elements of Reusable Object-Oriented Software", Author = "Erich Gamma" },
new Book { Id = new Guid("00000000-0000-0000-0000-000000000003"), Title = "Clean Code: A Handbook of Agile Software Craftsmanship", Author = "Robert C. Martin" },
new Book { Id = new Guid("00000000-0000-0000-0000-000000000004"), Title = "You Don't Know JS: ES6 & Beyond", Author = "Kyle Simpson" },
new Book { Id = new Guid("00000000-0000-0000-0000-000000000005"), Title = "The Pragmatic Programmer", Author = "Andrew Hunt" },
new Book { Id = new Guid("00000000-0000-0000-0000-000000000006"), Title = "Code: The Hidden Language of Computer Hardware and Software", Author = "Charles Petzold" },
new Book { Id = new Guid("00000000-0000-0000-0000-000000000007"), Title = "Structure and Interpretation of Computer Programs", Author = "Harold Abelson" },
new Book { Id = new Guid("00000000-0000-0000-0000-000000000008"), Title = "Refactoring: Improving the Design of Existing Code", Author = "Martin Fowler" },
new Book { Id = new Guid("00000000-0000-0000-0000-000000000009"), Title = "Cracking the Coding Interview: 189 Programming Questions and Solutions", Author = "Gayle Laakmann McDowell" },
new Book { Id = new Guid("00000000-0000-0000-0000-000000000010"), Title = "Grokking Algorithms: An illustrated guide for programmers and other curious people", Author = "Aditya Bhargava" }
});
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;
});
}
var _logger = app.Services.GetService<ILogger<Program>>();
app.MapGet(pattern: "/books", ([FromServices] List<Book> bookList, [FromServices] ILogger<Program> logger) =>
{
logger.LogInformation("books endpoint is called.");
return TypedResults.Ok(bookList);
})
.WithName("GetBooks")
.WithDescription("Fetches all books")
.WithGroupName("v1")
.WithSummary("Fetches all books")
.WithTags("Book Get Operations")
.WithOpenApi();
app.MapGet("/books/id/{id}", Results<Ok<Book>, NotFound> ([FromRoute] Guid id, [FromServices] List<Book> bookList, [FromServices] ILogger<Program> logger) =>
{
_logger?.LogInformation($"book by id endpoint is called. id is {id}");
logger.LogInformation("book by id endpoint is called.");
return bookList.FirstOrDefault(book => book.Id == id) is Book book
? TypedResults.Ok(book)
: TypedResults.NotFound();
})
.WithName("GetBooksById")
.WithDescription("Fetches books by id")
.WithGroupName("v1")
.WithSummary("Fetches books by id")
.WithTags("Book Get Operations")
.WithOpenApi();
app.MapGet("/books/author/{author}", Results<Ok<List<Book>>, NotFound> ([FromRoute] string author,
[FromServices] List<Book> bookList, [FromServices] ILogger<Program> logger) =>
{
logger.LogInformation($"Get book by author endpoint is called. author is {author}");
return bookList.Where(book => book.Author == author).ToList() is List<Book> booksByAuthor
? TypedResults.Ok(booksByAuthor)
: TypedResults.NotFound();
})
.WithName("GetBooksByAuthor")
.WithDescription("Fetches books by author")
.WithGroupName("v1")
.WithSummary("Fetches books by author")
.WithTags("Book Get Operations")
.WithOpenApi();
app.MapGet("/books/title/{title}", Results<Ok<List<Book>>, NotFound> ([FromRoute] string title, [FromServices] List<Book> bookList, [FromServices] ILogger<Program> logger) =>
{
logger.LogInformation($"Get book by title endpoint is called. title is {title}");
// null coalescing ?? operator is used
return bookList.Where(book => book.Title?.Contains(title, StringComparison.OrdinalIgnoreCase) ?? false).ToList() is
List<Book> booksByTitle
? TypedResults.Ok(booksByTitle)
: TypedResults.NotFound();
})
.WithName("GetBooksByTitle")
.WithDescription("Fetches books by title")
.WithGroupName("v1")
.WithSummary("Fetches books by title")
.WithTags("Book Get Operations")
.WithOpenApi();
app.MapPost("/book", Results<Created<Book>, BadRequest> ([FromBody] Book newBook, [FromServices] List<Book> bookList, [FromServices] ILogger<Program> logger) =>
{
logger.LogInformation("New book submission endpoint is called.");
if (bookList.Any(book => book.Title == newBook.Title))
{
return TypedResults.BadRequest();
}
newBook.Id = Guid.NewGuid();
bookList.Add(newBook);
return TypedResults.Created($"/book/{newBook.Id}", newBook);
})
.WithName("AddNewBook")
.WithDescription("Add new book")
.WithGroupName("v1")
.WithSummary("Add new book")
.WithTags("Book Set Operations")
.WithOpenApi();
app.MapPut("/book/{id}", Results<Ok<Book>, NotFound, BadRequest> ([FromRoute] Guid id, [FromBody] Book updatedBook, [FromServices] List<Book> bookList, [FromServices] ILogger<Program> logger) =>
{
logger.LogInformation("Book update endpoint is called.");
var existingBook = bookList.FirstOrDefault(book => book.Id == id);
if (existingBook is null)
{
return TypedResults.NotFound();
}
if (updatedBook.Id != id)
{
return TypedResults.BadRequest();
}
existingBook.Title = updatedBook.Title;
existingBook.Author = updatedBook.Author;
return TypedResults.Ok(existingBook);
})
.WithName("ReplaceExistingBook")
.WithDescription("Replace existing book")
.WithGroupName("v1")
.WithSummary("Replace existing book")
.WithTags("Book Set Operations")
.WithOpenApi();
app.MapDelete("/book/{id}", Results<Ok, NotFound> ([FromRoute] Guid id, [FromServices] List<Book> bookList, [FromServices] ILogger<Program> logger) =>
{
logger.LogInformation("Book deletion endpoint is called.");
var existingBook = bookList.FirstOrDefault(book => book.Id == id);
if (existingBook is null)
{
return TypedResults.NotFound();
}
bookList.Remove(existingBook);
return TypedResults.Ok();
})
.WithName("DeleteBook")
.WithDescription("Delete book")
.WithGroupName("v1")
.WithSummary("Delete book")
.WithTags("Book Set Operations")
.WithOpenApi();
app.Run();
internal class Book
{
public Guid Id { get; set; }
public string? Title { get; set; }
public string? Author { get; set; }
}
internal interface IDateTime
{
DateTime Now { get; }
}
internal class SystemDateTime : IDateTime
{
public DateTime Now => throw new NotImplementedException();
}
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.
Swagger UI will be hosted in https://localhost:7100/swagger/ and http://localhost:5203/swagger. Port number can be different because it is dynamic, check your debug terminal as shown below.
I sent request from Swagger UI to /book/{id}
endpoint so debugger stopped in line 34