[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/mMz_WsqS) :sectnums: :nofooter: :toc: left :icons: font :data-uri: :source-highlighter: highlightjs = Exc.02 -- Books Exceptions are such an easy topic that you are getting some more leeway with this exercise already. Additionally, we'll hone some of your other skills which might have gotten a bit rusty over the last weeks. And to top if off -- after working with it in WMC already -- you'll finally deal with `async/await` and `ValueTask` in this assignment. You are going to process book data, which is retrieved either from a file or a webservice. To make it easier for you both the CSV file and the web service are already done and have been provided. == Class Diagram Split into three parts to improve readability. .Loading [plantuml] ---- @startuml hide empty members interface IAsyncDisposable {} interface IEnumerable {} interface IBookLoader { +ValueTask LoadAsync() +bool LoadingDone [readonly] } abstract class LoaderBase { #IReadOnlyCollection? Books +bool LoadingDone [protected set] } class CsvDataLoader <> { -FileStream? _fileStream +CsvDataLoader(string) -ValueTask> LoadBooksAsync() [async] } class WebDataLoader <> { -HttpClient? _httpClient [readonly] -string _endpointUrl [readonly] +WebDataLoader(string, IHttpClientFactory) } interface IHttpClientFactory { +HttpClient CreateClient() } class HttpClientFactory <> {} IAsyncDisposable <|.. IBookLoader IEnumerable <|.. IBookLoader IBookLoader <|.. LoaderBase IHttpClientFactory <|.. HttpClientFactory LoaderBase <|-- CsvDataLoader LoaderBase <|-- WebDataLoader @enduml ---- .Exceptions [plantuml] ---- @startuml hide empty members class NotLoadedException <> { +NotLoadedException() } class CsvProcessingException <> { +CsvProcessingException(string) } class WebProcessingException <> { +WebProcessingException(string, Exception? = null) } class ISBNValidationException <> { +ISBNValidationException(string) } @enduml ---- .Book Store [plantuml] ---- @startuml hide empty members class Book <> { +string Title [readonly] +string Author [readonly] +string Publisher [readonly] +int Year [readonly] +string ISBN [readonly] +bool CheckISBNValid() {static} +void CheckISBNValid(string) } class BookStore <> { -string BooksCsvFilePath [const] -string BooksWebEndpoint [const] -ISet _allBooks [readonly] -IDictionary> _booksByAuthor [readonly] -IDictionary> _booksByPublisher [readonly] -IDictionary> _booksByYear [readonly] -LoaderType _loaderType [readonly] -bool _skipInvalids [readonly] -bool _dataLoaded +BookStore(LoaderType, bool) +ValueTask> GetAllBooksAsync(bool = false) [async] +ValueTask> GetBooksByAuthorAsync(string, bool = false) [async] +ValueTask> GetBooksByPublisherAsync(string, bool = false) [async] +ValueTask> GetBooksByYearAsync(int) [async] {static} -IReadOnlyCollection GetSortedList(IEnumerable, bool) -ValueTask EnsureDataLoaded() -ValueTask LoadDataAsync() [async] -void PopulateBookCollections(IBookLoader) } enum LoaderType { Web, Csv } class BookByYearComparer <> { -bool _descending [readonly] +BookByYearComparer(bool) {static} -int CompareAsc(Book?, Book?) } interface IComparer {} IComparer <|.. BookByYearComparer BookStore *-- LoaderType BookStore o-- Book @enduml ---- == Tasks === Learn about `async/await` and `ValueTask` In WMC you are already proficient with `async/await` and `Promise`. Now it is high time to learn how to use this concept in C# as well. * First, read through the following https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/[article describing the concept] ** That's quite a long article, but rest assured that I _will_ check if you actually read it 😎 * Then https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/[learn why `ValueTask` is preferable over `Task`] ** This is a very 'techy' article -- if you struggle it is sufficient if you remember to use `ValueTask` instead of `Task` everywhere except where an existing API requires `Task` TIP: Luckily `async/await` is practically the same and if you treat a `ValueTask` like a `Promise` you'll be fine. === Start the webservice You've been provided with a (already completed) webservice that will provide book data. However, to be able to use it you need to start it first -- just as you did in WMC with `json-server`. To do that use the following command: .Starting the server [source,bash] ---- # make sure you are within the 'server' directory dotnet run # alternatively run the run.ps1 script # don't close the terminal window! ---- You can verify that the server is running correctly by opening the http://localhost:5000/books[Books Endpoint]. === Implement the Application You have to understand resource management (`try/finally` and/or `using`) -- if that is not the case, please consult the slides or workshop material again. `ValueTask.CompletedTask` will also be useful. NOTE: Since you are going to write tests & documentation yourself anyway you are _allowed_ this time to change method signatures, remove or add methods, etc. -- as long as the functionality stays the same and the code is clean, readable, follows best practices and _works_. ==== Testing * No unit tests have been provided for this assignment * => You have to write your own tests! ** Remember to do TDD and commit after every step * Think about which parts can be tested, which parts can't and where a clever mock can help you out * A unit test project has already been created for you and sample data is linked ==== Book * Represents a simple book with some common properties and an https://en.wikipedia.org/wiki/ISBN[ISBN] * It has a _property_ which allows to validate the ISBN of the instance * And it has a _static_ method which allows to validate any ISBN ** You _only_ have to assume ISBN-13 format ** The validation algorithm is described https://en.wikipedia.org/wiki/ISBN#ISBN-13_check_digit_calculation[here] ===== Exceptions You have to throw the following `ISBNValidationException`: * ISBN is empty * ISBN has a length other than 13 * ISBN contains non-numeric characters * ISBN has an invalid check digit ==== Loader Base * Throws a `NotLoadedException` if the enumerator is retrieved without `LoadAsync` being called (and successfully executed) first ==== CSV Loader * _Not_ the same as all those CSV readers we've written before! * This time you _have_ to use a https://learn.microsoft.com/en-us/dotnet/api/system.io.filestream?view=net-9.0[`FileStream`] and close it properly in the `DisposeAsync` method ** And it has to be readonly * Hints: ** You will need both `FileMode.Open` & `FileAccess.Read` ** You are allowed to use a https://learn.microsoft.com/en-us/dotnet/api/system.io.streamreader?view=net-9.0[`StreamReader`] with the `FileStream` which will make consuming the stream much easier *** `using var reader = ...` ** Use the 'Async' versions of the methods and don't forget to `await` them ===== Exceptions You have to throw the following exceptions: * `FileNotFoundException` if the file does not exist ** Make sure to use the correct constructor (message and filename) * `InvalidOperationException` if loading is attempted a second time * `CsvProcessingException` if ** The file stream is null ** A null line is read from the stream before reaching EOF ** A line contains the wrong number of fields (columns) ** A value (in a column) is null or empty ** The year value is not a valid integer ==== Web Loader * Uses a `HttpClient` to _get_ data from the webservice ** Retrieved from the `IHttpClientFactory` during construction ** Properly disposed in the `DisposeAsync` method * Use the `GetFromJsonAsync` method to retrieve the data ===== Exceptions You have to throw the following exceptions: * `InvalidOperationException` if loading is attempted a second time * `WebProcessingException` if ** The `HttpClient` is null when loading data ** Any exception occurs during the `GetFromJsonAsync` call (wrap as inner exception) ==== Book Store * The book store(age) uses _either_ a `CsvLoader` or a `WebLoader` to load the data * Data is only loaded on first request (and then cached) ** We _avoid_ long-running operations in the constructor, which a file read or even web request would be * Upon loading the data is put into quick access collections ** By author ** By publisher ** By year * The user can access the books in the store by these paths (or get all) * The store will check for every book if its ISBN is valid -- the flag `skipInvalids` determines if invalid books are skipped (= not included in the catalogue) or not (kept, despite being invalid) * You _will_ need to do `await using IBookLoader loader = ...` ** Make sure you understand why we have to do `await using` instead of `using` here ===== Comparer One more time: create a comparer for books which can be used to sort them by year. Mind that, as usual, the user can specify ascending or descending order. === Write the documentation Pretty straight forward: Write the complete XMLDoc for the application (all non-private members). === Sample Run `Program` is already complete. Here is a simple run of the application: video::pics/sample_run.mp4[Sample Run,width=800] WARNING: Application won't compile initially, you have to create some types and assign some fields first.