From f236135bb849fb07d4c2f7f4f682009edd9d0769 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:02:44 +0100 Subject: [PATCH 1/9] Changed database from in-memory to postgres --- exercise.webapi/Data/DataContext.cs | 7 ++++--- exercise.webapi/Program.cs | 6 ++++-- exercise.webapi/appsettings.Development.json | 3 +-- exercise.webapi/appsettings.json | 3 +-- exercise.webapi/exercise.webapi.csproj | 1 + 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/exercise.webapi/Data/DataContext.cs b/exercise.webapi/Data/DataContext.cs index b6be7a9..4756e93 100644 --- a/exercise.webapi/Data/DataContext.cs +++ b/exercise.webapi/Data/DataContext.cs @@ -7,15 +7,16 @@ namespace exercise.webapi.Data { public class DataContext : DbContext { + private readonly IConfiguration _configuration; - public DataContext(DbContextOptions options) : base(options) + public DataContext(DbContextOptions options, IConfiguration configuration) : base(options) { - + _configuration = configuration; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseInMemoryDatabase("Library"); + optionsBuilder.UseNpgsql(_configuration.GetConnectionString("DefaultConnection")); } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/exercise.webapi/Program.cs b/exercise.webapi/Program.cs index 43dec56..45c2ce5 100644 --- a/exercise.webapi/Program.cs +++ b/exercise.webapi/Program.cs @@ -9,13 +9,15 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("Library")); +builder.Services.AddDbContext(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddScoped(); var app = builder.Build(); -using (var dbContext = new DataContext(new DbContextOptions())) +using (var scope = app.Services.CreateScope()) { + var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.Database.EnsureCreated(); } diff --git a/exercise.webapi/appsettings.Development.json b/exercise.webapi/appsettings.Development.json index 63d13d3..3c094af 100644 --- a/exercise.webapi/appsettings.Development.json +++ b/exercise.webapi/appsettings.Development.json @@ -8,7 +8,6 @@ "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnectionString": "Host=ep-winter-silence-a8e5oju7.eastus2.azure.neon.tech; Database=neondb; Username=neondb_owner; Password=npg_nBmYdDo0Lr3z;" - + "DefaultConnection": "Host=localhost;Database=db;Username=user;Password=pass;" } } \ No newline at end of file diff --git a/exercise.webapi/appsettings.json b/exercise.webapi/appsettings.json index 63d13d3..3c094af 100644 --- a/exercise.webapi/appsettings.json +++ b/exercise.webapi/appsettings.json @@ -8,7 +8,6 @@ "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnectionString": "Host=ep-winter-silence-a8e5oju7.eastus2.azure.neon.tech; Database=neondb; Username=neondb_owner; Password=npg_nBmYdDo0Lr3z;" - + "DefaultConnection": "Host=localhost;Database=db;Username=user;Password=pass;" } } \ No newline at end of file diff --git a/exercise.webapi/exercise.webapi.csproj b/exercise.webapi/exercise.webapi.csproj index 0ff4269..a9a55df 100644 --- a/exercise.webapi/exercise.webapi.csproj +++ b/exercise.webapi/exercise.webapi.csproj @@ -10,6 +10,7 @@ + From 0dc2c55d46ae89b3f365fcf50c7060cd075d8d2e Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:17:51 +0100 Subject: [PATCH 2/9] Ignoring warning on migration, as dynamic data isn't supposed to work --- exercise.webapi/Data/DataContext.cs | 4 ++-- exercise.webapi/exercise.webapi.csproj | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/exercise.webapi/Data/DataContext.cs b/exercise.webapi/Data/DataContext.cs index 4756e93..790818a 100644 --- a/exercise.webapi/Data/DataContext.cs +++ b/exercise.webapi/Data/DataContext.cs @@ -1,7 +1,6 @@ using exercise.webapi.Models; using Microsoft.EntityFrameworkCore; -using System.Collections.Generic; -using System.Reflection.Emit; +using Microsoft.EntityFrameworkCore.Diagnostics; namespace exercise.webapi.Data { @@ -17,6 +16,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseNpgsql(_configuration.GetConnectionString("DefaultConnection")); + optionsBuilder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/exercise.webapi/exercise.webapi.csproj b/exercise.webapi/exercise.webapi.csproj index a9a55df..d90ae5d 100644 --- a/exercise.webapi/exercise.webapi.csproj +++ b/exercise.webapi/exercise.webapi.csproj @@ -9,6 +9,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + From 334116e7002a62a5f2cd3bdf99e09e077b6eb097 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:06:05 +0100 Subject: [PATCH 3/9] Book API done --- exercise.webapi/Dto/AuthorResponse.cs | 21 +++++++++++++++++++ exercise.webapi/Dto/BookResponse.cs | 19 +++++++++++++++++ exercise.webapi/Endpoints/BookApi.cs | 18 ++++++++++++++-- exercise.webapi/Repository/BookRepository.cs | 6 ++++++ exercise.webapi/Repository/IBookRepository.cs | 1 + 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 exercise.webapi/Dto/AuthorResponse.cs create mode 100644 exercise.webapi/Dto/BookResponse.cs diff --git a/exercise.webapi/Dto/AuthorResponse.cs b/exercise.webapi/Dto/AuthorResponse.cs new file mode 100644 index 0000000..343a46a --- /dev/null +++ b/exercise.webapi/Dto/AuthorResponse.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace exercise.webapi.Dto; + +public class AuthorResponse +{ + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public BookResponse[]? Books { get; set; } + + public AuthorResponse(Models.Author author) + { + Id = author.Id; + FirstName = author.FirstName; + LastName = author.LastName; + Email = author.Email; + } +} \ No newline at end of file diff --git a/exercise.webapi/Dto/BookResponse.cs b/exercise.webapi/Dto/BookResponse.cs new file mode 100644 index 0000000..4016a12 --- /dev/null +++ b/exercise.webapi/Dto/BookResponse.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; +using exercise.webapi.Models; + +namespace exercise.webapi.Dto; + +public class BookResponse +{ + public int Id { get; set; } + public string Title { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public AuthorResponse? Author { get; set; } + + public BookResponse(Book book) + { + Id = book.Id; + Title = book.Title; + Author = new AuthorResponse(book.Author); + } +} \ No newline at end of file diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 6758215..0dcbb27 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -1,4 +1,5 @@ -using exercise.webapi.Models; +using exercise.webapi.Dto; +using exercise.webapi.Models; using exercise.webapi.Repository; using static System.Reflection.Metadata.BlobBuilder; @@ -9,12 +10,25 @@ public static class BookApi public static void ConfigureBooksApi(this WebApplication app) { app.MapGet("/books", GetBooks); + app.MapGet("/books/{id}", GetBook); } private static async Task GetBooks(IBookRepository bookRepository) { var books = await bookRepository.GetAllBooks(); - return TypedResults.Ok(books); + var response = books.Select(b => new BookResponse(b)); + + return TypedResults.Ok(response); + } + + private static async Task GetBook(IBookRepository bookRepository, int id) + { + var book = await bookRepository.GetBookById(id); + if (book == null) + { + return TypedResults.NotFound(); + } + return TypedResults.Ok(new BookResponse(book)); } } } diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index 1f5e64a..c26579e 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -18,5 +18,11 @@ public async Task> GetAllBooks() return await _db.Books.Include(b => b.Author).ToListAsync(); } + + public async Task GetBookById(int id) + { + return await _db.Books.Include(b => b.Author).FirstOrDefaultAsync(b => b.Id == id); + + } } } diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs index f860016..a973413 100644 --- a/exercise.webapi/Repository/IBookRepository.cs +++ b/exercise.webapi/Repository/IBookRepository.cs @@ -5,5 +5,6 @@ namespace exercise.webapi.Repository public interface IBookRepository { public Task> GetAllBooks(); + Task GetBookById(int id); } } From c2ab7e549bca2b568debb387b0145b1a387d806a Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:14:46 +0100 Subject: [PATCH 4/9] Improvements to Swagger documentation --- exercise.webapi/Endpoints/BookApi.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 0dcbb27..e172eed 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -1,7 +1,6 @@ using exercise.webapi.Dto; -using exercise.webapi.Models; using exercise.webapi.Repository; -using static System.Reflection.Metadata.BlobBuilder; +using Microsoft.AspNetCore.Mvc; namespace exercise.webapi.Endpoints { @@ -13,6 +12,7 @@ public static void ConfigureBooksApi(this WebApplication app) app.MapGet("/books/{id}", GetBook); } + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] private static async Task GetBooks(IBookRepository bookRepository) { var books = await bookRepository.GetAllBooks(); @@ -21,6 +21,8 @@ private static async Task GetBooks(IBookRepository bookRepository) return TypedResults.Ok(response); } + [ProducesResponseType(typeof(BookResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] private static async Task GetBook(IBookRepository bookRepository, int id) { var book = await bookRepository.GetBookById(id); From c2db36c9448529c1f5a0368d5aa37f818def8d25 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:48:49 +0100 Subject: [PATCH 5/9] Author API finished --- exercise.webapi/Dto/AuthorResponse.cs | 8 +++++ exercise.webapi/Dto/BookResponse.cs | 6 +++- exercise.webapi/Endpoints/AuthorApi.cs | 36 +++++++++++++++++++ exercise.webapi/Endpoints/BookApi.cs | 8 ++++- exercise.webapi/Program.cs | 3 ++ .../Repository/AuthorRepository.cs | 31 ++++++++++++++++ exercise.webapi/Repository/BookRepository.cs | 18 ++++++++-- .../Repository/IAuthorRepository.cs | 9 +++++ 8 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 exercise.webapi/Endpoints/AuthorApi.cs create mode 100644 exercise.webapi/Repository/AuthorRepository.cs create mode 100644 exercise.webapi/Repository/IAuthorRepository.cs diff --git a/exercise.webapi/Dto/AuthorResponse.cs b/exercise.webapi/Dto/AuthorResponse.cs index 343a46a..e7250ed 100644 --- a/exercise.webapi/Dto/AuthorResponse.cs +++ b/exercise.webapi/Dto/AuthorResponse.cs @@ -17,5 +17,13 @@ public AuthorResponse(Models.Author author) FirstName = author.FirstName; LastName = author.LastName; Email = author.Email; + if (author.Books.Count > 0) + { + Books = author.Books.Select((b) => + { + b.Author = null; + return new BookResponse(b); + }).ToArray(); + } } } \ No newline at end of file diff --git a/exercise.webapi/Dto/BookResponse.cs b/exercise.webapi/Dto/BookResponse.cs index 4016a12..560a37e 100644 --- a/exercise.webapi/Dto/BookResponse.cs +++ b/exercise.webapi/Dto/BookResponse.cs @@ -14,6 +14,10 @@ public BookResponse(Book book) { Id = book.Id; Title = book.Title; - Author = new AuthorResponse(book.Author); + // TODO: Throws NullReferenceException when not in the if statement. FIX! + if (book.Author is not null) + { + Author = new AuthorResponse(book.Author); + } } } \ No newline at end of file diff --git a/exercise.webapi/Endpoints/AuthorApi.cs b/exercise.webapi/Endpoints/AuthorApi.cs new file mode 100644 index 0000000..f476710 --- /dev/null +++ b/exercise.webapi/Endpoints/AuthorApi.cs @@ -0,0 +1,36 @@ +using exercise.webapi.Dto; +using exercise.webapi.Repository; +using Microsoft.AspNetCore.Mvc; + +namespace exercise.webapi.Endpoints; + +public static class AuthorApi +{ + public static void ConfigureAuthorsApi(this WebApplication app) + { + app.MapGet("/authors", GetAuthors); + app.MapGet("/authors/{id}", GetAuthor); + } + + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + private static async Task GetAuthors(IAuthorRepository authorRepository) + { + var authors = await authorRepository.GetAllAuthors(); + var response = authors.Select(a => new AuthorResponse(a)); + + return TypedResults.Ok(response); + } + + [ProducesResponseType(typeof(AuthorResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task GetAuthor(IAuthorRepository authorRepository, int id) + { + var author = await authorRepository.GetAuthorById(id); + if (author == null) + { + return TypedResults.NotFound(); + } + + return TypedResults.Ok(new AuthorResponse(author)); + } +} \ No newline at end of file diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index e172eed..6c5ffd3 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -1,4 +1,5 @@ using exercise.webapi.Dto; +using exercise.webapi.Models; using exercise.webapi.Repository; using Microsoft.AspNetCore.Mvc; @@ -17,7 +18,11 @@ private static async Task GetBooks(IBookRepository bookRepository) { var books = await bookRepository.GetAllBooks(); var response = books.Select(b => new BookResponse(b)); - + + foreach (var book in books) + { + book.Author.Books = new List(); + } return TypedResults.Ok(response); } @@ -30,6 +35,7 @@ private static async Task GetBook(IBookRepository bookRepository, int i { return TypedResults.NotFound(); } + book.Author.Books = new List(); return TypedResults.Ok(new BookResponse(book)); } } diff --git a/exercise.webapi/Program.cs b/exercise.webapi/Program.cs index 45c2ce5..a8351be 100644 --- a/exercise.webapi/Program.cs +++ b/exercise.webapi/Program.cs @@ -12,6 +12,8 @@ builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddScoped(); +builder.Services.AddScoped(); + var app = builder.Build(); @@ -31,4 +33,5 @@ app.UseHttpsRedirection(); app.ConfigureBooksApi(); +app.ConfigureAuthorsApi(); app.Run(); diff --git a/exercise.webapi/Repository/AuthorRepository.cs b/exercise.webapi/Repository/AuthorRepository.cs new file mode 100644 index 0000000..23009f4 --- /dev/null +++ b/exercise.webapi/Repository/AuthorRepository.cs @@ -0,0 +1,31 @@ +using exercise.webapi.Data; +using exercise.webapi.Models; +using Microsoft.EntityFrameworkCore; + +namespace exercise.webapi.Repository; + +public class AuthorRepository : IAuthorRepository +{ + DataContext _db; + + public AuthorRepository(DataContext db) + { + _db = db; + } + + public async Task> GetAllAuthors() + { + return await _db.Authors + .Include(a => a.Books) + .ToListAsync(); + } + + public async Task GetAuthorById(int id) + { + var author = await _db.Authors + .Include(a => a.Books) + .FirstOrDefaultAsync(a => a.Id == id); + + return author; + } +} \ No newline at end of file diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index c26579e..bd32fcd 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -15,14 +15,26 @@ public BookRepository(DataContext db) public async Task> GetAllBooks() { - return await _db.Books.Include(b => b.Author).ToListAsync(); + return await _db.Books + .Include(b => b.Author) + .ToListAsync(); } public async Task GetBookById(int id) { - return await _db.Books.Include(b => b.Author).FirstOrDefaultAsync(b => b.Id == id); - + return await _db.Books.Select(b => new Book + { + Id = b.Id, + Title = b.Title, + Author = new Author + { + Id = b.Author.Id, + FirstName = b.Author.FirstName, + LastName = b.Author.LastName, + Email = b.Author.Email + } + }).FirstOrDefaultAsync(b => b.Id == id); } } } diff --git a/exercise.webapi/Repository/IAuthorRepository.cs b/exercise.webapi/Repository/IAuthorRepository.cs new file mode 100644 index 0000000..95cbda5 --- /dev/null +++ b/exercise.webapi/Repository/IAuthorRepository.cs @@ -0,0 +1,9 @@ +using exercise.webapi.Models; + +namespace exercise.webapi.Repository; + +public interface IAuthorRepository +{ + public Task> GetAllAuthors(); + Task GetAuthorById(int id); +} \ No newline at end of file From 1100997778b768b56fb08b0ec958b672b69b3e19 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:00:58 +0100 Subject: [PATCH 6/9] Additions to Book API - DELETE --- exercise.webapi/Endpoints/BookApi.cs | 14 ++++++++++++++ exercise.webapi/Repository/BookRepository.cs | 6 ++++++ exercise.webapi/Repository/IBookRepository.cs | 1 + 3 files changed, 21 insertions(+) diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 6c5ffd3..e1f6ac9 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -11,6 +11,7 @@ public static void ConfigureBooksApi(this WebApplication app) { app.MapGet("/books", GetBooks); app.MapGet("/books/{id}", GetBook); + app.MapDelete("/books/{id}", DeleteBook); } [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] @@ -38,5 +39,18 @@ private static async Task GetBook(IBookRepository bookRepository, int i book.Author.Books = new List(); return TypedResults.Ok(new BookResponse(book)); } + + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task DeleteBook(IBookRepository bookRepository, int id) + { + var book = await bookRepository.GetBookById(id); + if (book == null) + { + return TypedResults.NotFound(); + } + bookRepository.DeleteBook(book); + return TypedResults.NoContent(); + } } } diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index bd32fcd..c1ab307 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -36,5 +36,11 @@ public async Task> GetAllBooks() } }).FirstOrDefaultAsync(b => b.Id == id); } + + public void DeleteBook(Book book) + { + _db.Books.Remove(book); + _db.SaveChanges(); + } } } diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs index a973413..9bd774f 100644 --- a/exercise.webapi/Repository/IBookRepository.cs +++ b/exercise.webapi/Repository/IBookRepository.cs @@ -6,5 +6,6 @@ public interface IBookRepository { public Task> GetAllBooks(); Task GetBookById(int id); + void DeleteBook(Book book); } } From 3ab6bbdb1d9c2f9ea0f57d869919b0c0b5ab9e01 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:36:33 +0100 Subject: [PATCH 7/9] Update book finally works --- exercise.webapi/Dto/BookPut.cs | 11 +++++++++++ exercise.webapi/Endpoints/BookApi.cs | 18 ++++++++++++++++++ exercise.webapi/Repository/BookRepository.cs | 8 ++++++++ exercise.webapi/Repository/IBookRepository.cs | 1 + 4 files changed, 38 insertions(+) create mode 100644 exercise.webapi/Dto/BookPut.cs diff --git a/exercise.webapi/Dto/BookPut.cs b/exercise.webapi/Dto/BookPut.cs new file mode 100644 index 0000000..ba36ff3 --- /dev/null +++ b/exercise.webapi/Dto/BookPut.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace exercise.webapi.Dto; + +public class BookPut +{ + [MinLength(1)] + [MaxLength(100)] + public string Title { get; set; } + public int AuthorId { get; set; } +} \ No newline at end of file diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index e1f6ac9..10918d3 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -12,6 +12,7 @@ public static void ConfigureBooksApi(this WebApplication app) app.MapGet("/books", GetBooks); app.MapGet("/books/{id}", GetBook); app.MapDelete("/books/{id}", DeleteBook); + app.MapPut("/books/{id}", UpdateBook); } [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] @@ -52,5 +53,22 @@ private static async Task DeleteBook(IBookRepository bookRepository, in bookRepository.DeleteBook(book); return TypedResults.NoContent(); } + + private static async Task UpdateBook(IBookRepository bookRepository, IAuthorRepository authorRepository, int id, [FromBody] BookPut book) + { + var existingBook = await bookRepository.GetBookById(id); + if (existingBook == null) + { + return TypedResults.NotFound(); + } + + existingBook.Title = book.Title; + Console.WriteLine(book.AuthorId); + existingBook.AuthorId = book.AuthorId; + var newBook = await bookRepository.UpdateBook(existingBook); + + newBook.Author.Books = new List(); + return TypedResults.Ok(new BookResponse(newBook)); + } } } diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index c1ab307..46e9133 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -42,5 +42,13 @@ public void DeleteBook(Book book) _db.Books.Remove(book); _db.SaveChanges(); } + + public async Task UpdateBook(Book book) + { + //_db.Books.Update(book); + _db.Attach(book).State = EntityState.Modified; + await _db.SaveChangesAsync(); + return book; + } } } diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs index 9bd774f..bdc4b03 100644 --- a/exercise.webapi/Repository/IBookRepository.cs +++ b/exercise.webapi/Repository/IBookRepository.cs @@ -7,5 +7,6 @@ public interface IBookRepository public Task> GetAllBooks(); Task GetBookById(int id); void DeleteBook(Book book); + Task UpdateBook(Book book); } } From a92718eabc4789826e2ce1252c36d36d236b1d25 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:23:34 +0100 Subject: [PATCH 8/9] Book POST handler created. Validators included. Exercise done --- exercise.webapi/Dto/BookPost.cs | 13 +++++++ exercise.webapi/Endpoints/BookApi.cs | 35 ++++++++++++++++++- exercise.webapi/Repository/BookRepository.cs | 7 ++++ exercise.webapi/Repository/IBookRepository.cs | 1 + 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 exercise.webapi/Dto/BookPost.cs diff --git a/exercise.webapi/Dto/BookPost.cs b/exercise.webapi/Dto/BookPost.cs new file mode 100644 index 0000000..ae84d09 --- /dev/null +++ b/exercise.webapi/Dto/BookPost.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace exercise.webapi.Dto; + +public class BookPost +{ + [Required] + [MinLength(1)] + [MaxLength(100)] + public string Title { get; set; } + [Required] + public int AuthorId { get; set; } +} \ No newline at end of file diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index 10918d3..a62141d 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -1,4 +1,5 @@ -using exercise.webapi.Dto; +using System.ComponentModel.DataAnnotations; +using exercise.webapi.Dto; using exercise.webapi.Models; using exercise.webapi.Repository; using Microsoft.AspNetCore.Mvc; @@ -11,6 +12,7 @@ public static void ConfigureBooksApi(this WebApplication app) { app.MapGet("/books", GetBooks); app.MapGet("/books/{id}", GetBook); + app.MapPost("/books", CreateBook); app.MapDelete("/books/{id}", DeleteBook); app.MapPut("/books/{id}", UpdateBook); } @@ -70,5 +72,36 @@ private static async Task UpdateBook(IBookRepository bookRepository, IA newBook.Author.Books = new List(); return TypedResults.Ok(new BookResponse(newBook)); } + + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task CreateBook(IBookRepository bookRepository, + IAuthorRepository authorRepository, [FromBody] BookPost book) + { + try + { + Validator.ValidateObject(book, new ValidationContext(book), true); + } + catch (ValidationException e) + { + return TypedResults.BadRequest(e.Message); + } + + var author = await authorRepository.GetAuthorById(book.AuthorId); + if (author == null) + { + return TypedResults.NotFound(); + } + + var newBook = new Book + { + Title = book.Title, + AuthorId = book.AuthorId + }; + var createdBook = await bookRepository.AddBook(newBook); + createdBook.Author.Books = new List(); + return TypedResults.Created("/books/" + createdBook.Id); + } } } diff --git a/exercise.webapi/Repository/BookRepository.cs b/exercise.webapi/Repository/BookRepository.cs index 46e9133..1e196c7 100644 --- a/exercise.webapi/Repository/BookRepository.cs +++ b/exercise.webapi/Repository/BookRepository.cs @@ -12,6 +12,13 @@ public BookRepository(DataContext db) { _db = db; } + + public async Task AddBook(Book book) + { + await _db.Books.AddAsync(book); + await _db.SaveChangesAsync(); + return book; + } public async Task> GetAllBooks() { diff --git a/exercise.webapi/Repository/IBookRepository.cs b/exercise.webapi/Repository/IBookRepository.cs index bdc4b03..cd66446 100644 --- a/exercise.webapi/Repository/IBookRepository.cs +++ b/exercise.webapi/Repository/IBookRepository.cs @@ -4,6 +4,7 @@ namespace exercise.webapi.Repository { public interface IBookRepository { + Task AddBook(Book book); public Task> GetAllBooks(); Task GetBookById(int id); void DeleteBook(Book book); From 5e6e909ada8029cc8b8a2df6e001783024dc9cfb Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:24:05 +0100 Subject: [PATCH 9/9] Enhancements to Swagger stuff --- exercise.webapi/Endpoints/BookApi.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercise.webapi/Endpoints/BookApi.cs b/exercise.webapi/Endpoints/BookApi.cs index a62141d..4ae12a5 100644 --- a/exercise.webapi/Endpoints/BookApi.cs +++ b/exercise.webapi/Endpoints/BookApi.cs @@ -56,6 +56,8 @@ private static async Task DeleteBook(IBookRepository bookRepository, in return TypedResults.NoContent(); } + [ProducesResponseType(typeof(BookResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] private static async Task UpdateBook(IBookRepository bookRepository, IAuthorRepository authorRepository, int id, [FromBody] BookPut book) { var existingBook = await bookRepository.GetBookById(id);