Skip to content

Jone Hjorteland #104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,15 @@ MigrationBackup/
.ionide/

# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd

*/**/bin/Debug
*/**/bin/Release
*/**/obj/Debug
*/**/obj/Release
*/Migrations
/workshop.wwwapi/appsettings.json
/workshop.wwwapi/appsettings.Development.json

appsettings.json
appsettings.Development.json
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# C# Entity Framework Intro

1. Fork this repository
2. Clone your fork to your machine
3. Open the ef.intro.sln in Visual Studio

## Setup



- Note the .gitignore file in the root of the project which prevents the build directories being uploaded:
```
*/**/bin/Debug
*/**/bin/Release
*/**/obj/Debug
*/Migrations
*/**/obj/Release
/workshop.wwwapi/appsettings.json
/workshop.wwwapi/appsettings.Development.json
```


## Dependencies

Some of these have already been installed:

- Install-Package Scalar.AspNetCore
- provides a /scalar endpoint
- Install-Package Swashbuckle.AspNetCore
- provides a /swagger endpoint

- Install-Package Microsoft.EntityFrameworkCore
- Install-Package Microsoft.EntityFrameworkCore.Design
- Install-Package Microsoft.EntityFrameworkCore.InMemory

## Core and Extension are combined into the following requirements

The overall objective is to complete the BookAPI CRUD operations, using DTO objects to return nicely formatted json without any cyclical serialization errors.

As guidelines we suggest:

- implement the GET book and GET all books. When you return the books objects, use an appropriate DTO to return the book + author (but no nested books inside author). Make sure to include the authors when you load the data in the repository.
- implement the UPDATE boook where you can change the author via id (you may skip updating other properties like title, etc); make sure to return the Book + Author once the update is done
- implement the DELETE book
- implement the CREATE book - it should return NotFound when author id is not valid and BadRequest when book object not valid

- implement the author API (interested in just the GET, GET all) -> the author should return the list of books, use its own author response DTO


Extensions (each one is one extension, implement at least one):

- Add a publisher model and add that as an additional relation to the book, where a book has one publisher; make sure to update the seeder and to create a publisher API Endpoints with the GET and GET all endpoints. Getting a Publisher should return all books that have that publisher + the author for each book. Update the author endpoint to return the Book + Publisher; Updaate the book endpoint to return the Book + Author + Publisher.
- Update the model to have many to many relation between Book and Author, where a Book can have 1 or more authors. Update all the endpoints to return the Book + Authors list.
- Add endpoints for assigning / removing an author from a Book
- Users of the library want to be able to checkout books for a period of time. Add a model to capture which books have been checked out. Include the checkout date and expected return date. Create a checkout api where you can try to checkout a book. You cannot check out books that are currently already borrowed. Add api routes for displaying books that are currently checked out, books that are overdue (should have been returned but are not returned). When you checkout a book, return the expected date for the return (2 weeks). To achieve this, you may want to look at query parameters for the filtering of the checked out books `?filter=someValue`.
- Swap out he InMemory db for another Postgres instance such as [neon](https://neon.tech/). Make sure you have ```appsettings.json``` AND ```appsettings.Development.json``` files in the root of the workshop.wwwapi project which contains suitable credentials. You may modify the seeding process if you need to.
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",

"ConnectionStrings": {
"DefaultConnectionString": "Host=HOST; Database=DATABASE; Username=USERNAME; Password=PASSWORD;"

}
}

```

Super Extensions (for the brave!)

- Introduce the ability for Users to submit Book Reviews, which consist of a 5 star rating and comment on their view of the book. Users can submit these reviews anonymously or leave their email address.
- Implement both a generic IRepository<T> AND Repository<T>. Before you commit to this ensure that you understand the implications of the DbSet Include method on the generic repository!


31 changes: 31 additions & 0 deletions exercise.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BF2A625B-9E2A-4434-834B-8858AB4F5AD7}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "exercise.webapi", "exercise.webapi\exercise.webapi.csproj", "{E9C7E4C6-D843-4276-A6DE-C06FFE315453}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E9C7E4C6-D843-4276-A6DE-C06FFE315453}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9C7E4C6-D843-4276-A6DE-C06FFE315453}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9C7E4C6-D843-4276-A6DE-C06FFE315453}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9C7E4C6-D843-4276-A6DE-C06FFE315453}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9036C2A1-9F9D-4565-BE3F-411FE1341E8D}
EndGlobalSection
EndGlobal
31 changes: 31 additions & 0 deletions exercise.webapi/DTOs/AuthorDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using exercise.webapi.Models;

namespace exercise.webapi.DTOs
{
public class AuthorDTO
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public List<BookListDTO> Books { get; set; }

public AuthorDTO(Author author)
{
Id = author.Id;
FirstName = author.FirstName;
LastName = author.LastName;
Email = author.Email;
Books = author.Books?.Select(b => new BookListDTO(b)).ToList() ?? new List<BookListDTO>();
}

public AuthorDTO(int id, string firstName, string lastName, string email, List<BookListDTO> books = null)
{
Id = id;
FirstName = firstName;
LastName = lastName;
Email = email;
Books = books ?? new List<BookListDTO>();
}
}
}
17 changes: 17 additions & 0 deletions exercise.webapi/DTOs/BookDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using exercise.webapi.Models;

namespace exercise.webapi.DTOs
{
public class BookDTO
{
public int Id { get; set; }
public string Title { get; set; }
public AuthorDTO Author { get; set; }
public BookDTO(Book book)
{
Id = book.Id;
Title = book.Title;
Author = new AuthorDTO(book.Author);
}
}
}
15 changes: 15 additions & 0 deletions exercise.webapi/DTOs/BookListDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using exercise.webapi.Models;

namespace exercise.webapi.DTOs
{
public class BookListDTO
{
public int Id { get; set; }
public string Title { get; set; }
public BookListDTO(Book book)
{
Id = book.Id;
Title = book.Title;
}
}
}
8 changes: 8 additions & 0 deletions exercise.webapi/DTOs/CreateBookDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace exercise.webapi.DTOs
{
public class CreateBookDTO
{
public string Title { get; set; }
public int AuthorId { get; set; }
}
}
39 changes: 39 additions & 0 deletions exercise.webapi/Data/DataContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using exercise.webapi.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.IO;

namespace exercise.webapi.Data
{
public class DataContext : DbContext
{
private readonly IConfiguration _configuration;

public DataContext(DbContextOptions<DataContext> options) : base(options)
{
_configuration = new ConfigurationBuilder()
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), ".."))
.AddJsonFile("appsettings.json")
.Build();
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
var connectionString = _configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseNpgsql(connectionString);
}
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Seeder seeder = new Seeder();
modelBuilder.Entity<Author>().HasData(seeder.Authors);
modelBuilder.Entity<Book>().HasData(seeder.Books);
}

public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
}
}
117 changes: 117 additions & 0 deletions exercise.webapi/Data/Seeder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using exercise.webapi.Models;

namespace exercise.webapi.Data
{
public class Seeder
{
private List<string> _firstnames = new List<string>()
{
"Audrey",
"Donald",
"Elvis",
"Barack",
"Oprah",
"Jimi",
"Mick",
"Kate",
"Charles",
"Kate"
};
private List<string> _lastnames = new List<string>()
{
"Hepburn",
"Trump",
"Presley",
"Obama",
"Winfrey",
"Hendrix",
"Jagger",
"Winslet",
"Windsor",
"Middleton"

};
private List<string> _domain = new List<string>()
{
"bbc.co.uk",
"google.com",
"theworld.ca",
"something.com",
"tesla.com",
"nasa.org.us",
"gov.us",
"gov.gr",
"gov.nl",
"gov.ru"
};
private List<string> _firstword = new List<string>()
{
"The",
"Two",
"Several",
"Fifteen",
"A bunch of",
"An army of",
"A herd of"


};
private List<string> _secondword = new List<string>()
{
"Orange",
"Purple",
"Large",
"Microscopic",
"Green",
"Transparent",
"Rose Smelling",
"Bitter"
};
private List<string> _thirdword = new List<string>()
{
"Buildings",
"Cars",
"Planets",
"Houses",
"Flowers",
"Leopards"
};

private List<Author> _authors = new List<Author>();
private List<Book> _books = new List<Book>();

public Seeder()
{

Random authorRandom = new Random();
Random bookRandom = new Random();



for (int x = 1; x < 250; x++)
{
Author author = new Author();
author.Id = x;
author.FirstName = _firstnames[authorRandom.Next(_firstnames.Count)];
author.LastName = _lastnames[authorRandom.Next(_lastnames.Count)];
author.Email = $"{author.FirstName}.{author.LastName}@{_domain[authorRandom.Next(_domain.Count)]}".ToLower();
_authors.Add(author);
}


for (int y = 1; y < 250; y++)
{
Book book = new Book();
book.Id = y;
book.Title = $"{_firstword[bookRandom.Next(_firstword.Count)]} {_secondword[bookRandom.Next(_secondword.Count)]} {_thirdword[bookRandom.Next(_thirdword.Count)]}";
book.AuthorId = _authors[authorRandom.Next(_authors.Count)].Id;
//book.Author = authors[book.AuthorId-1];
_books.Add(book);
}


}
public List<Author> Authors { get { return _authors; } }
public List<Book> Books { get { return _books; } }
}
}
33 changes: 33 additions & 0 deletions exercise.webapi/Endpoints/AuthorApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using exercise.webapi.Repository;
using Microsoft.AspNetCore.Mvc;

namespace exercise.webapi.Endpoints
{
public static class AuthorApi
{
public static void ConfigureAuthorApi(this WebApplication app)
{
app.MapGet("/authors", GetAuthors);
app.MapGet("/authors/{id}", GetAuthorById);
}

private static async Task<IResult> GetAuthors([FromServices] IAuthorRepository authorRepository)
{
var authors = await authorRepository.GetAllAuthors();
return Results.Ok(authors);
}

private static async Task<IResult> GetAuthorById([FromServices] IAuthorRepository authorRepository, int id)
{
try
{
var author = await authorRepository.GetAuthorById(id);
return Results.Ok(author);
}
catch (KeyNotFoundException ex)
{
return Results.NotFound(new { message = ex.Message });
}
}
}
}
Loading