Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ public async Task Run()
}
}

/// <summary>
/// Handles the patron search state. Prompts the user for a patron name,
/// searches the repository, and validates the results before proceeding.
/// </summary>
/// <returns>
/// ConsoleState.PatronSearch if no results found or too many results (>20);
/// ConsoleState.PatronSearchResults if valid results are found.
/// </returns>
async Task<ConsoleState> PatronSearch()
{
string searchInput = ReadPatronName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async Task<LoanReturnStatus> ReturnLoan(int loanId)
await _loanRepository.UpdateLoan(loan);
return LoanReturnStatus.Success;
}
catch (Exception e)
catch (Exception)
{
return LoanReturnStatus.Error;
}
Expand Down Expand Up @@ -62,7 +62,7 @@ public async Task<LoanExtensionStatus> ExtendLoan(int loanId)
await _loanRepository.UpdateLoan(loan);
return LoanExtensionStatus.Success;
}
catch (Exception e)
catch (Exception)
{
return LoanExtensionStatus.Error;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public async Task<MembershipRenewalStatus> RenewMembership(int patronId)
try{
await _patronRepository.UpdatePatron(patron);
return MembershipRenewalStatus.Success;
} catch (Exception e) {
} catch (Exception) {
return MembershipRenewalStatus.Error;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public enum CommonActions
SearchPatrons = 4,
RenewPatronMembership = 8,
ReturnLoanedBook = 16,
ExtendLoanedBook = 32
ExtendLoanedBook = 32,
SearchBooks = 64
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Library.ApplicationCore.Entities;
using Library.ApplicationCore.Enums;
using Library.Console;
using Library.Infrastructure.Data;

public class ConsoleApp
{
Expand All @@ -16,13 +17,15 @@ public class ConsoleApp
ILoanRepository _loanRepository;
ILoanService _loanService;
IPatronService _patronService;
JsonData _jsonData;

public ConsoleApp(ILoanService loanService, IPatronService patronService, IPatronRepository patronRepository, ILoanRepository loanRepository)
public ConsoleApp(ILoanService loanService, IPatronService patronService, IPatronRepository patronRepository, ILoanRepository loanRepository, JsonData jsonData)
{
_patronRepository = patronRepository;
_loanRepository = loanRepository;
_loanService = loanService;
_patronService = patronService;
_jsonData = jsonData;
}

public async Task Run()
Expand Down Expand Up @@ -139,6 +142,7 @@ static CommonActions ReadInputOptions(CommonActions options, out int optionNumbe
"m" when options.HasFlag(CommonActions.RenewPatronMembership) => CommonActions.RenewPatronMembership,
"e" when options.HasFlag(CommonActions.ExtendLoanedBook) => CommonActions.ExtendLoanedBook,
"r" when options.HasFlag(CommonActions.ReturnLoanedBook) => CommonActions.ReturnLoanedBook,
"b" when options.HasFlag(CommonActions.SearchBooks) => CommonActions.SearchBooks,
_ when int.TryParse(userInput, out optionNumber) => CommonActions.Select,
_ => CommonActions.Repeat
};
Expand Down Expand Up @@ -178,10 +182,16 @@ static void WriteInputOptions(CommonActions options)
{
Console.WriteLine("Or type a number to select a list item.");
}
if (options.HasFlag(CommonActions.SearchBooks))
{
Console.WriteLine(" - \"b\" to check for book availability");
}
}

async Task<ConsoleState> PatronDetails()
{
if (selectedPatronDetails == null)
throw new InvalidOperationException("selectedPatronDetails is null.");
Console.WriteLine($"Name: {selectedPatronDetails.Name}");
Console.WriteLine($"Membership Expiration: {selectedPatronDetails.MembershipEnd}");
Console.WriteLine();
Expand All @@ -193,7 +203,7 @@ async Task<ConsoleState> PatronDetails()
loanNumber++;
}

CommonActions options = CommonActions.SearchPatrons | CommonActions.Quit | CommonActions.Select | CommonActions.RenewPatronMembership;
CommonActions options = CommonActions.SearchPatrons | CommonActions.Quit | CommonActions.Select | CommonActions.RenewPatronMembership | CommonActions.SearchBooks;
CommonActions action = ReadInputOptions(options, out int selectedLoanNumber);
if (action == CommonActions.Select)
{
Expand Down Expand Up @@ -225,12 +235,18 @@ async Task<ConsoleState> PatronDetails()
selectedPatronDetails = (await _patronRepository.GetPatron(selectedPatronDetails.Id))!;
return ConsoleState.PatronDetails;
}
else if (action == CommonActions.SearchBooks)
{
return await SearchBooks();
}

throw new InvalidOperationException("An input option is not handled.");
}

async Task<ConsoleState> LoanDetails()
{
if (selectedPatronDetails == null)
throw new InvalidOperationException("selectedPatronDetails is null.");
Console.WriteLine($"Book title: {selectedLoanDetails.BookItem!.Book!.Title}");
Console.WriteLine($"Book Author: {selectedLoanDetails.BookItem!.Book!.Author!.Name}");
Console.WriteLine($"Due date: {selectedLoanDetails.DueDate}");
Expand All @@ -257,7 +273,7 @@ async Task<ConsoleState> LoanDetails()
Console.WriteLine(EnumHelper.GetDescription(status));
_currentState = ConsoleState.LoanDetails;
// reload loan after returning
selectedLoanDetails = await _loanRepository.GetLoan(selectedLoanDetails.Id);
selectedLoanDetails = (await _loanRepository.GetLoan(selectedLoanDetails.Id))!;
return ConsoleState.LoanDetails;
}
else if (action == CommonActions.Quit)
Expand All @@ -271,4 +287,42 @@ async Task<ConsoleState> LoanDetails()

throw new InvalidOperationException("An input option is not handled.");
}

async Task<ConsoleState> SearchBooks()
{
string? bookTitle = null;
while (string.IsNullOrWhiteSpace(bookTitle))
{
Console.Write("Enter a book title to search for: ");
bookTitle = Console.ReadLine();
}

await _jsonData.EnsureDataLoaded();

var book = _jsonData.Books!.FirstOrDefault(b => b.Title.Contains(bookTitle, StringComparison.OrdinalIgnoreCase));
if (book == null)
{
Console.WriteLine($"No book found with title containing '{bookTitle}'.");
return ConsoleState.PatronDetails;
}

var bookItem = _jsonData.BookItems!.FirstOrDefault(bi => bi.BookId == book.Id);
if (bookItem == null)
{
Console.WriteLine($"No copy of '{book.Title}' found in the library.");
return ConsoleState.PatronDetails;
}

var activeLoan = _jsonData.Loans!.FirstOrDefault(l => l.BookItemId == bookItem.Id && l.ReturnDate == null);
if (activeLoan == null)
{
Console.WriteLine($"'{book.Title}' is available for loan.");
}
else
{
Console.WriteLine($"'{book.Title}' is on loan to another patron. The return due date is {activeLoan.DueDate:d}.");
}

return ConsoleState.PatronDetails;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public async Task<List<Patron>> SearchPatrons(string searchInput)
await _jsonData.EnsureDataLoaded();

List<Patron> searchResults = new List<Patron>();
foreach (Patron patron in _jsonData.Patrons)
foreach (Patron patron in _jsonData.Patrons!)
{
if (patron.Name.Contains(searchInput))
{
Expand Down Expand Up @@ -50,7 +50,7 @@ public async Task UpdatePatron(Patron patron)
{
await _jsonData.EnsureDataLoaded();
var patrons = _jsonData.Patrons!;
Patron existingPatron = null;
Patron? existingPatron = null;
foreach (var p in patrons)
{
if (p.Id == patron.Id)
Expand Down
155 changes: 146 additions & 9 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,151 @@
# Microsoft Lab Exercises
<!-- Change the title above as appropriate -->
Searched codebase for "I need you to generate the contents of a README.md file that I can use for the current code repository. Use "Library App" as the project title. The README file should include the following sections: Description, Project Structure, Key Classes and Interfaces, Usage, License. Format all sections as raw markdown. Use a bullet list with indents to represent the project structure. Do not include ".gitignore" or the ".github", "bin", and "obj" folders. I want add the suggested content to the README.md file that's open in the editor.", 9 results

This repo contains exercises and supporting files for Microsoft skilling content.
I’m going to quickly inspect the current project structure in the target lab folder so I can generate an accurate README in raw markdown that matches your requested sections and exclusions.

The exercises may be used in both self-paced skilling experiences on [Microsoft Learn](https://learn.microsoft.com) and in Microsoft authorized instructor-led training.
<!-- Update thr paragraph above with a link to a specific Learning Path or course as appropriate -->
I’m now checking the actual folder layout of the Library App project so the README content reflects real paths, classes, and run commands instead of a generic template.

## Information for MCTs
<!-- You can remove this section if the exercises will not be used to support Microsoft Official Curriculum ILT -->
Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot)

**Are you an MCT?** - Have a look at our [GitHub User Guide for MCTs](https://microsoftlearning.github.io/MCT-User-Guide/)
Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src)

Any MCT (Microsoft Certified Trainer) can submit a pull request to the code or content in the GitHub repro. Microsoft and the course author will then triage and include content and lab code changes as needed. You can submit bugs, changes, improvement, and ideas. Find a new Azure or Microsoft 365 feature before we have? Submit a new demo!
Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/tests)

I found the top-level layout and next I’m drilling into key folders to capture accurate class and interface names for the Key Classes and Interfaces section.

Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.ApplicationCore)

Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console)

Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Infrastructure)

Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/tests/UnitTests)

I have the folder map; now I’m extracting actual class/interface symbols so the README names are correct and not guessed.

Searched for regex `\b(interface|class)\s+[A-Za-z0-9_]+` (`**/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.ApplicationCore/**/*.cs`), 12 results

Searched for regex `\b(class)\s+[A-Za-z0-9_]+` (`**/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/**/*.cs`), 1 result

Searched for regex `\b(class)\s+[A-Za-z0-9_]+` (`**/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Infrastructure/**/*.cs`), 3 results

I’ve confirmed the main symbols; next I’ll capture a few more folder details like JSON data files so the project structure and usage instructions are complete.

Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Infrastructure/Data)

Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/Json)

Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/tests/UnitTests/ApplicationCore)

Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/tests/UnitTests/ApplicationCore/LoanService)

Read [](file:///c%3A/VS%20Workspaces/learn-github-copilot/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/tests/UnitTests/ApplicationCore/PatronService)

Searched for regex `<TargetFramework>|<PackageReference` (`**/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/**/*.csproj`), 14 results

Searched for files matching `**/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/**/*.sln`, no matches

# Library App

## Description

Library App is a modular .NET console application for managing core library operations, including patron lookup, loan tracking, book returns, and membership renewal workflows.
The solution follows a clean architecture style by separating domain logic, console interaction, and data access into dedicated projects.
Data persistence is implemented with JSON-backed repositories, making the application easy to run locally for development, testing, and training scenarios.

## Project Structure

- AccelerateDevGHCopilot/
- src/
- Library.ApplicationCore/
- Entities/
- Author.cs
- Book.cs
- BookItem.cs
- Loan.cs
- Patron.cs
- Enums/
- EnumHelper.cs
- LoanExtensionStatus.cs
- LoanReturnStatus.cs
- MembershipRenewalStatus.cs
- Interfaces/
- ILoanRepository.cs
- ILoanService.cs
- IPatronRepository.cs
- IPatronService.cs
- Services/
- LoanService.cs
- PatronService.cs
- Library.ApplicationCore.csproj
- Library.Console/
- CommonActions.cs
- ConsoleApp.cs
- ConsoleState.cs
- Program.cs
- appSettings.json
- Json/
- Authors.json
- Books.json
- BookItems.json
- Loans.json
- Patrons.json
- Library.Console.csproj
- Library.Infrastructure/
- Data/
- JsonData.cs
- JsonLoanRepository.cs
- JsonPatronRepository.cs
- Library.Infrastructure.csproj
- tests/
- UnitTests/
- ApplicationCore/
- LoanService/
- ExtendLoan.cs
- ReturnLoan.cs
- PatronService/
- RenewMembership.cs
- LoanFactory.cs
- PatronFactory.cs
- UnitTests.csproj
- README.md

## Key Classes and Interfaces

- Entities
- Author, Book, BookItem, Loan, Patron: Domain models that represent library records and transactions.
- Interfaces
- ILoanRepository: Contract for loan data access operations.
- IPatronRepository: Contract for patron data access operations.
- ILoanService: Contract for loan business workflows.
- IPatronService: Contract for patron business workflows.
- Services
- LoanService: Implements checkout, return, and extension logic.
- PatronService: Implements patron search and membership renewal logic.
- Console Layer
- ConsoleApp: Coordinates user interaction and application flow.
- ConsoleState and CommonActions: Define UI states and available actions.
- Infrastructure Layer
- JsonData: Loads and saves JSON data sources.
- JsonLoanRepository and JsonPatronRepository: Repository implementations using JSON files.

## Usage

1. Prerequisites:
- .NET SDK 9.0 or later

2. From the repository root, restore and build:

dotnet restore src/Library.Console/Library.Console.csproj
dotnet build src/Library.Console/Library.Console.csproj

3. Run the console application:

dotnet run --project src/Library.Console/Library.Console.csproj

4. Run unit tests:

dotnet test tests/UnitTests/UnitTests.csproj

## License

This project is licensed under the MIT License. See the LICENSE file for details.