From d4e44866548b5ca2662669d7ce61aacecad480e5 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 13:10:31 +0100 Subject: [PATCH 01/42] Installed spectre console GUI package --- .../DocumentProcessor.JJHH17.csproj | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj new file mode 100644 index 0000000..fe930e6 --- /dev/null +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + From ff1942b182ba2a9e7a1c0ca920ee2aa84be5e09a Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 13:17:32 +0100 Subject: [PATCH 02/42] Added initial program.cs class, includes spectre menu --- .../DocumentProcessor.JJHH17/Program.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs new file mode 100644 index 0000000..8fab74b --- /dev/null +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -0,0 +1,65 @@ +using System; +using Spectre.Console; + +namespace DocumentProcessor.JJHH17; + +public class Program +{ + public static void Main(string[] args) + { + Menu(); + } + + enum MenuOptions + { + AddEntry, + Read, + Delete, + Update, + Exit + } + + public static void Menu() + { + bool running = true; + while (running) + { + AnsiConsole.MarkupLine("[bold yellow]Document Processor Menu[/]"); + Console.Clear(); + + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select an option:") + .AddChoices(Enum.GetValues())); + + switch (choice) + { + case MenuOptions.AddEntry: + Console.WriteLine("Add Entry selected."); + Console.ReadKey(); + break; + + case MenuOptions.Read: + Console.WriteLine("Read selected."); + Console.ReadKey(); + break; + + case MenuOptions.Delete: + Console.WriteLine("Delete selected."); + Console.ReadKey(); + break; + + case MenuOptions.Update: + Console.WriteLine("Update selected."); + Console.ReadKey(); + break; + + case MenuOptions.Exit: + Console.WriteLine("Press any key to exit..."); + Console.ReadKey(); + running = false; + break; + } + } + } +} \ No newline at end of file From 85b297676764a34a05d2081af380e9e0046b91ba Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 13:23:11 +0100 Subject: [PATCH 03/42] Added the system.config nuget package --- .../DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj index fe930e6..b74245a 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj @@ -10,6 +10,7 @@ + From 91456d7f76450376f8ea1ef8a7d48b8f244ccda5 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 13:25:47 +0100 Subject: [PATCH 04/42] Installed the EF to SQL Server Nuget Package --- .../DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj index b74245a..949e855 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj @@ -8,6 +8,7 @@ + From dffc8f765dc40bbb829f5795cb0ad479367b0755 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 13:29:43 +0100 Subject: [PATCH 05/42] Created connection string and database values --- .../DocumentProcessor.JJHH17/Configurations/App.config | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config new file mode 100644 index 0000000..173209e --- /dev/null +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From 67ed932a1a9cce46f42e512b4e054681826e4e73 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 13:34:42 +0100 Subject: [PATCH 06/42] Created initial program model and DB Context --- .../Models/Phonebook.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs new file mode 100644 index 0000000..f13c4c4 --- /dev/null +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs @@ -0,0 +1,26 @@ +using System.Configuration; +using Microsoft.EntityFrameworkCore; + +namespace DocumentProcessor.JJHH17.Models; + +public class Phonebook +{ + public int Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public string PhoneNumber { get; set; } +} + +public class PhoneBookContext : DbContext +{ + private static readonly string server = ConfigurationManager.AppSettings["Server"]; + private static readonly string databaseInstance = ConfigurationManager.AppSettings["DatabaseInstance"]; + public static string connectionString = $@"Server=({server})\{databaseInstance};Integrated Security=true;"; + + public DbSet Phonebooks { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlServer(connectionString); + } +} \ No newline at end of file From 7812529eca931abef48e9dc6ddffedc4af298b99 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 16:09:48 +0100 Subject: [PATCH 07/42] Installed entity framework design package for running migrations --- .../DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj index 949e855..10eeb43 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj @@ -8,6 +8,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + From 30bdd0c08f73831073ab21f261a46eb2d4c3105f Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 16:10:03 +0100 Subject: [PATCH 08/42] Createdmethods for adding new entries to db --- .../DocumentProcessor.JJHH17/Program.cs | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index 8fab74b..aaf2e1f 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -1,9 +1,10 @@ using System; +using DocumentProcessor.JJHH17.Models; using Spectre.Console; namespace DocumentProcessor.JJHH17; -public class Program +public class Program { public static void Main(string[] args) { @@ -35,8 +36,8 @@ public static void Menu() switch (choice) { case MenuOptions.AddEntry: - Console.WriteLine("Add Entry selected."); - Console.ReadKey(); + Console.Clear(); + AddEntry(); break; case MenuOptions.Read: @@ -62,4 +63,72 @@ public static void Menu() } } } + + public static void AddEntry() + { + AnsiConsole.MarkupLine("[bold yellow]Add Entry[/]"); + Console.WriteLine("Enter name:"); + string name = Console.ReadLine(); + string email = EmailInput(); + string phoneNumber = PhoneNumberInput(); + + using (var context = new PhoneBookContext()) + { + var newEntry = new Phonebook + { + Name = name, + Email = email, + PhoneNumber = phoneNumber + }; + + context.Phonebooks.Add(newEntry); + context.SaveChanges(); + } + + AnsiConsole.MarkupLine("[green]Entry added successfully![/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + + public static string EmailInput() + { + string email; + while (true) + { + Console.WriteLine("Enter email address:"); + email = Console.ReadLine(); + + if (email.Contains("@") && email.Contains(".")) + { + break; + } + else + { + AnsiConsole.MarkupLine("[red]Invalid email format. Please try again.[/]"); + } + } + + return email; + } + + public static string PhoneNumberInput() + { + string phoneNumber; + while (true) + { + Console.WriteLine("Enter phohne number (must be 11 digits):"); + phoneNumber = Console.ReadLine(); + + if (phoneNumber.Length == 11 && long.TryParse(phoneNumber, out _)) + { + break; + } + else + { + AnsiConsole.MarkupLine("[red]Invalid phone number. Please enter exactly 11 digits.[/]"); + } + } + + return phoneNumber; + } } \ No newline at end of file From e62370efdf6fe84a4e8912245fa9bdb78aed5133 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 16:10:27 +0100 Subject: [PATCH 09/42] Resolved typo in connection string call --- .../DocumentProcessor.JJHH17/Models/Phonebook.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs index f13c4c4..560092c 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs @@ -14,7 +14,7 @@ public class Phonebook public class PhoneBookContext : DbContext { private static readonly string server = ConfigurationManager.AppSettings["Server"]; - private static readonly string databaseInstance = ConfigurationManager.AppSettings["DatabaseInstance"]; + private static readonly string databaseInstance = ConfigurationManager.AppSettings["DatabaseName"]; public static string connectionString = $@"Server=({server})\{databaseInstance};Integrated Security=true;"; public DbSet Phonebooks { get; set; } From 80ec3ad21720a7262cbf8a4bd0fc977554b6b6f9 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 16:18:14 +0100 Subject: [PATCH 10/42] Added method for printing entries to console --- .../DocumentProcessor.JJHH17/Program.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index aaf2e1f..9d6c93d 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -41,7 +41,9 @@ public static void Menu() break; case MenuOptions.Read: - Console.WriteLine("Read selected."); + Console.Clear(); + ReadEntries(); + Console.WriteLine("Press any key to return to the menu..."); Console.ReadKey(); break; @@ -90,6 +92,33 @@ public static void AddEntry() Console.ReadKey(); } + public static void ReadEntries() + { + using (var context = new PhoneBookContext()) + { + var query = context.Phonebooks.ToList(); + + if (query.Count == 0) + { + AnsiConsole.MarkupLine("[yellow]No entries were found[/]"); + } + else + { + var table = new Table(); + table.AddColumn("ID"); + table.AddColumn("Name"); + table.AddColumn("Email"); + table.AddColumn("Phone Number"); + + foreach (var entry in query) + { + table.AddRow(entry.Id.ToString(), entry.Name, entry.Email, entry.PhoneNumber); + } + AnsiConsole.Write(table); + } + } + } + public static string EmailInput() { string email; From 804101fedf7687c7f863292baff9c0d7a1271705 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 16:25:05 +0100 Subject: [PATCH 11/42] Added method for deleting entries --- .../DocumentProcessor.JJHH17/Program.cs | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index 9d6c93d..6bfefec 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -16,7 +16,6 @@ enum MenuOptions AddEntry, Read, Delete, - Update, Exit } @@ -48,13 +47,8 @@ public static void Menu() break; case MenuOptions.Delete: - Console.WriteLine("Delete selected."); - Console.ReadKey(); - break; - - case MenuOptions.Update: - Console.WriteLine("Update selected."); - Console.ReadKey(); + Console.Clear(); + DeleteEntry(); break; case MenuOptions.Exit: @@ -119,6 +113,38 @@ public static void ReadEntries() } } + public static void DeleteEntry() + { + using (var context = new PhoneBookContext()) + { + AnsiConsole.MarkupLine("[bold yellow]Delete Entry[/]"); + ReadEntries(); + Console.WriteLine("Enter the ID of the entry to delete:"); + + if (int.TryParse(Console.ReadLine(), out int id)) + { + var entry = context.Phonebooks.Find(id); + if (entry != null) + { + context.Phonebooks.Remove(entry); + context.SaveChanges(); + AnsiConsole.MarkupLine("[green]Entry deleted successfully![/]"); + } + else + { + AnsiConsole.MarkupLine("[red]Entry not found.[/]"); + } + } + else + { + AnsiConsole.MarkupLine("[red]Invalid ID format.[/]"); + } + + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + } + public static string EmailInput() { string email; @@ -145,7 +171,7 @@ public static string PhoneNumberInput() string phoneNumber; while (true) { - Console.WriteLine("Enter phohne number (must be 11 digits):"); + Console.WriteLine("Enter phone number (must be 11 digits):"); phoneNumber = Console.ReadLine(); if (phoneNumber.Length == 11 && long.TryParse(phoneNumber, out _)) From 7ce04e4cb235893b19914515c0e2edc2f947e8af Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 14 Oct 2025 21:14:51 +0100 Subject: [PATCH 12/42] Added method that seeds data from a csv file --- .../DocumentProcessor.JJHH17/Program.cs | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index 6bfefec..441ebef 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -1,4 +1,4 @@ -using System; +using System.IO; using DocumentProcessor.JJHH17.Models; using Spectre.Console; @@ -21,6 +21,16 @@ enum MenuOptions public static void Menu() { + // Seeds data if database is empty + using (var context = new PhoneBookContext()) + { + if (!context.Phonebooks.Any()) + { + SeedCSVData(); + } + } + + bool running = true; while (running) { @@ -186,4 +196,47 @@ public static string PhoneNumberInput() return phoneNumber; } + + public static List ReadCSVFile(string filePath) + { + List rows = new List(); + + try + { + string[] lines = File.ReadAllLines(filePath); + + foreach (var line in lines) + { + var values = line.Split(','); + rows.Add(values); + } + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error reading file: {ex.Message}[/]"); + } + + return rows; + } + + public static void SeedCSVData() + { + string csvFilePath = "Import Data - Sheet1.csv"; + List csvData = ReadCSVFile(csvFilePath); + + foreach (string[] row in csvData) + { + using (var context = new PhoneBookContext()) + { + var newEntry = new Phonebook + { + Name = row[0], + Email = row[1], + PhoneNumber = row[2] + }; + context.Phonebooks.Add(newEntry); + context.SaveChanges(); + } + } + } } \ No newline at end of file From 64d19c863cbeeb01416f06e1ca8776bb8d68da0a Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Wed, 15 Oct 2025 13:00:25 +0100 Subject: [PATCH 13/42] Installed the CSV helper package --- .../DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj index 10eeb43..7e9b3cc 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj @@ -8,6 +8,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all From 602bf11a4bc37bdcbcad1887a4f234fc94698562 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Wed, 15 Oct 2025 13:08:59 +0100 Subject: [PATCH 14/42] Added string for export PDF file path location --- .../DocumentProcessor.JJHH17/Configurations/App.config | 1 + 1 file changed, 1 insertion(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config index 173209e..2ed45fa 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config @@ -3,5 +3,6 @@ + \ No newline at end of file From 56eb50f73eb55b315c276add9f2b5d4d85736fe8 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Wed, 15 Oct 2025 13:20:21 +0100 Subject: [PATCH 15/42] Removed configuration for filepath which was unused --- .../DocumentProcessor.JJHH17/Configurations/App.config | 1 - 1 file changed, 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config index 2ed45fa..173209e 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config @@ -3,6 +3,5 @@ - \ No newline at end of file From 6f30e61f7698e151bd9cb358f98be3dfa9866a1f Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Wed, 15 Oct 2025 13:20:34 +0100 Subject: [PATCH 16/42] Added code to export data to a PDF via app --- .../DocumentProcessor.JJHH17/Program.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index 441ebef..55ca2eb 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -1,6 +1,7 @@ using System.IO; using DocumentProcessor.JJHH17.Models; using Spectre.Console; +using CsvHelper; namespace DocumentProcessor.JJHH17; @@ -16,6 +17,7 @@ enum MenuOptions AddEntry, Read, Delete, + Export, Exit } @@ -61,6 +63,13 @@ public static void Menu() DeleteEntry(); break; + case MenuOptions.Export: + Console.Clear(); + CreateExportCSV(); + Console.WriteLine("Enter any key to return to the menu..."); + Console.ReadKey(); + break; + case MenuOptions.Exit: Console.WriteLine("Press any key to exit..."); Console.ReadKey(); @@ -239,4 +248,19 @@ public static void SeedCSVData() } } } + + public static void CreateExportCSV() + { + using (var context = new PhoneBookContext()) + { + var entries = context.Phonebooks.ToList(); + using (var writer = new StreamWriter("ExportedPhonebook.pdf")) + using (var csv = new CsvWriter(writer, System.Globalization.CultureInfo.InvariantCulture)) + { + csv.WriteRecords(entries); + } + } + AnsiConsole.MarkupLine("[green]Data exported to ExportedPhonebook.pdf successfully![/]"); + AnsiConsole.MarkupLine("[green]You can find the CSV in 'CodeReviews.Console.DocumentProcessor\\DocumentProcessor.JJHH17\\DocumentProcessor.JJHH17\\bin\\Debug\\net8.0\\ExportedPhonebook.pdf\'[/]"); + } } \ No newline at end of file From 5b031f36f01d1fbc560e9b88fe7ca196bf649541 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Wed, 15 Oct 2025 20:05:19 +0100 Subject: [PATCH 17/42] Added method for importing data via xls file --- .../DocumentProcessor.JJHH17/Program.cs | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index 55ca2eb..2dfcc02 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -28,7 +28,7 @@ public static void Menu() { if (!context.Phonebooks.Any()) { - SeedCSVData(); + SeedOption(); } } @@ -206,7 +206,38 @@ public static string PhoneNumberInput() return phoneNumber; } - public static List ReadCSVFile(string filePath) + enum FileTypes + { + CSV, + XLS, + XLSX + } + + public static void SeedOption() + { + Console.Clear(); + AnsiConsole.MarkupLine("[bold yellow]Seed Database via a given file type[/]"); + + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select a file type to import from:") + .AddChoices(Enum.GetValues())); + + switch (choice) + { + case FileTypes.CSV: + SeedCSVData(); + break; + case FileTypes.XLS: + SeedXLSData(); + break; + case FileTypes.XLSX: + AnsiConsole.MarkupLine("[red]XLSX import not implemented yet.[/]"); + break; + } + } + + public static List ReadFile(string filePath) { List rows = new List(); @@ -223,6 +254,8 @@ public static List ReadCSVFile(string filePath) catch (Exception ex) { AnsiConsole.MarkupLine($"[red]Error reading file: {ex.Message}[/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); } return rows; @@ -231,7 +264,7 @@ public static List ReadCSVFile(string filePath) public static void SeedCSVData() { string csvFilePath = "Import Data - Sheet1.csv"; - List csvData = ReadCSVFile(csvFilePath); + List csvData = ReadFile(csvFilePath); foreach (string[] row in csvData) { @@ -249,6 +282,27 @@ public static void SeedCSVData() } } + public static void SeedXLSData() + { + string xlsFilePath = "Import Data - Sheet1.xls"; + List xlsData = ReadFile(xlsFilePath); + + foreach (string[] row in xlsData) + { + using (var context = new PhoneBookContext()) + { + var newEntry = new Phonebook + { + Name = row[0], + Email = row[1], + PhoneNumber = row[2] + }; + context.Phonebooks.Add(newEntry); + context.SaveChanges(); + } + } + } + public static void CreateExportCSV() { using (var context = new PhoneBookContext()) From 094fa4a3e2c2d6a2e0d29ec5944d8d1556d26428 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Wed, 15 Oct 2025 21:08:42 +0100 Subject: [PATCH 18/42] Added helper packages for xlsx processing and reading --- .../DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj index 7e9b3cc..404e5b2 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj @@ -9,11 +9,14 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive all + From c89e76bca4d9f0157a864aec30a3f4a4d50683e2 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Wed, 15 Oct 2025 21:08:56 +0100 Subject: [PATCH 19/42] Added method to read and seed xlsx files --- .../DocumentProcessor.JJHH17/Program.cs | 92 ++++++++++++++++--- 1 file changed, 81 insertions(+), 11 deletions(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index 2dfcc02..48bcaea 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -2,6 +2,8 @@ using DocumentProcessor.JJHH17.Models; using Spectre.Console; using CsvHelper; +using System.Data; +using ExcelDataReader; namespace DocumentProcessor.JJHH17; @@ -232,7 +234,7 @@ public static void SeedOption() SeedXLSData(); break; case FileTypes.XLSX: - AnsiConsole.MarkupLine("[red]XLSX import not implemented yet.[/]"); + SeedXLSXData(); break; } } @@ -261,12 +263,72 @@ public static List ReadFile(string filePath) return rows; } + public static List ReadXlsx(string filePath) + { + System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); + + using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read); + using var reader = ExcelReaderFactory.CreateReader(stream); + + var dataset = reader.AsDataSet(new ExcelDataSetConfiguration + { + ConfigureDataTable = _ => new ExcelDataTableConfiguration + { + UseHeaderRow = true + } + }); + + var table = dataset.Tables[0]; + var rows = new List(); + + foreach (DataRow dr in table.Rows) + { + var name = dr["Name"].ToString()?.Trim(); + var email = dr["Email"].ToString()?.Trim(); + var phoneNumber = dr["PhoneNumber"].ToString()?.Trim(); + + rows.Add(new string[] { name, email, phoneNumber }); + } + + return rows; + } + public static void SeedCSVData() { - string csvFilePath = "Import Data - Sheet1.csv"; - List csvData = ReadFile(csvFilePath); + try + { + string csvFilePath = "Import Data - Sheet1.csv"; + List csvData = ReadFile(csvFilePath); + + foreach (string[] row in csvData) + { + using (var context = new PhoneBookContext()) + { + var newEntry = new Phonebook + { + Name = row[0], + Email = row[1], + PhoneNumber = row[2] + }; + context.Phonebooks.Add(newEntry); + context.SaveChanges(); + } + } + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error reading CSV file: {ex.Message}[/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + } + + public static void SeedXLSData() + { + string xlsFilePath = "Import Data - Sheet1.xls"; + List xlsData = ReadFile(xlsFilePath); - foreach (string[] row in csvData) + foreach (string[] row in xlsData) { using (var context = new PhoneBookContext()) { @@ -282,14 +344,15 @@ public static void SeedCSVData() } } - public static void SeedXLSData() + public static void SeedXLSXData() { - string xlsFilePath = "Import Data - Sheet1.xls"; - List xlsData = ReadFile(xlsFilePath); - - foreach (string[] row in xlsData) + try { - using (var context = new PhoneBookContext()) + string xlsxFilePath = "Import Data - Sheet1.xlsx"; + var xlsxData = ReadXlsx(xlsxFilePath); + + using var context = new PhoneBookContext(); + foreach (var row in xlsxData) { var newEntry = new Phonebook { @@ -298,8 +361,15 @@ public static void SeedXLSData() PhoneNumber = row[2] }; context.Phonebooks.Add(newEntry); - context.SaveChanges(); } + + context.SaveChanges(); + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error reading XLSX file: {ex.Message}[/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); } } From 97c231718574bac25639e9a3d20558c137f5d9a1 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Wed, 15 Oct 2025 21:16:20 +0100 Subject: [PATCH 20/42] Added method to seed from XLS files --- .../DocumentProcessor.JJHH17/Program.cs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index 48bcaea..ac06ce6 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -263,7 +263,7 @@ public static List ReadFile(string filePath) return rows; } - public static List ReadXlsx(string filePath) + public static List ReadExcel(string filePath) { System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); @@ -314,6 +314,10 @@ public static void SeedCSVData() context.SaveChanges(); } } + + AnsiConsole.MarkupLine("[green]Database seeded successfully from CSV![/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); } catch (Exception ex) { @@ -325,12 +329,12 @@ public static void SeedCSVData() public static void SeedXLSData() { - string xlsFilePath = "Import Data - Sheet1.xls"; - List xlsData = ReadFile(xlsFilePath); - - foreach (string[] row in xlsData) + try { - using (var context = new PhoneBookContext()) + string xlsFilePath = "Import Data - Sheet1.xls"; + var xlsData = ReadExcel(xlsFilePath); + using var context = new PhoneBookContext(); + foreach (var row in xlsData) { var newEntry = new Phonebook { @@ -339,8 +343,17 @@ public static void SeedXLSData() PhoneNumber = row[2] }; context.Phonebooks.Add(newEntry); - context.SaveChanges(); } + context.SaveChanges(); + AnsiConsole.MarkupLine("[green]Database seeded successfully from XLS![/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error reading XLS file: {ex.Message}[/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); } } @@ -349,7 +362,7 @@ public static void SeedXLSXData() try { string xlsxFilePath = "Import Data - Sheet1.xlsx"; - var xlsxData = ReadXlsx(xlsxFilePath); + var xlsxData = ReadExcel(xlsxFilePath); using var context = new PhoneBookContext(); foreach (var row in xlsxData) @@ -364,6 +377,9 @@ public static void SeedXLSXData() } context.SaveChanges(); + AnsiConsole.MarkupLine("[green]Database seeded successfully from XLSX![/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); } catch (Exception ex) { From 3a98ab06649b4963e461b4481798aeaa573ca8e0 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 15:16:51 +0100 Subject: [PATCH 21/42] Removed UserInterface and placed into its own class --- .../DocumentProcessor.JJHH17/Program.cs | 198 +----------------- 1 file changed, 2 insertions(+), 196 deletions(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index ac06ce6..931429a 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -1,4 +1,4 @@ -using System.IO; +using DocumentProcessor.JJHH17.UserInterface; using DocumentProcessor.JJHH17.Models; using Spectre.Console; using CsvHelper; @@ -11,201 +11,7 @@ public class Program { public static void Main(string[] args) { - Menu(); - } - - enum MenuOptions - { - AddEntry, - Read, - Delete, - Export, - Exit - } - - public static void Menu() - { - // Seeds data if database is empty - using (var context = new PhoneBookContext()) - { - if (!context.Phonebooks.Any()) - { - SeedOption(); - } - } - - - bool running = true; - while (running) - { - AnsiConsole.MarkupLine("[bold yellow]Document Processor Menu[/]"); - Console.Clear(); - - var choice = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("Select an option:") - .AddChoices(Enum.GetValues())); - - switch (choice) - { - case MenuOptions.AddEntry: - Console.Clear(); - AddEntry(); - break; - - case MenuOptions.Read: - Console.Clear(); - ReadEntries(); - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - break; - - case MenuOptions.Delete: - Console.Clear(); - DeleteEntry(); - break; - - case MenuOptions.Export: - Console.Clear(); - CreateExportCSV(); - Console.WriteLine("Enter any key to return to the menu..."); - Console.ReadKey(); - break; - - case MenuOptions.Exit: - Console.WriteLine("Press any key to exit..."); - Console.ReadKey(); - running = false; - break; - } - } - } - - public static void AddEntry() - { - AnsiConsole.MarkupLine("[bold yellow]Add Entry[/]"); - Console.WriteLine("Enter name:"); - string name = Console.ReadLine(); - string email = EmailInput(); - string phoneNumber = PhoneNumberInput(); - - using (var context = new PhoneBookContext()) - { - var newEntry = new Phonebook - { - Name = name, - Email = email, - PhoneNumber = phoneNumber - }; - - context.Phonebooks.Add(newEntry); - context.SaveChanges(); - } - - AnsiConsole.MarkupLine("[green]Entry added successfully![/]"); - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - } - - public static void ReadEntries() - { - using (var context = new PhoneBookContext()) - { - var query = context.Phonebooks.ToList(); - - if (query.Count == 0) - { - AnsiConsole.MarkupLine("[yellow]No entries were found[/]"); - } - else - { - var table = new Table(); - table.AddColumn("ID"); - table.AddColumn("Name"); - table.AddColumn("Email"); - table.AddColumn("Phone Number"); - - foreach (var entry in query) - { - table.AddRow(entry.Id.ToString(), entry.Name, entry.Email, entry.PhoneNumber); - } - AnsiConsole.Write(table); - } - } - } - - public static void DeleteEntry() - { - using (var context = new PhoneBookContext()) - { - AnsiConsole.MarkupLine("[bold yellow]Delete Entry[/]"); - ReadEntries(); - Console.WriteLine("Enter the ID of the entry to delete:"); - - if (int.TryParse(Console.ReadLine(), out int id)) - { - var entry = context.Phonebooks.Find(id); - if (entry != null) - { - context.Phonebooks.Remove(entry); - context.SaveChanges(); - AnsiConsole.MarkupLine("[green]Entry deleted successfully![/]"); - } - else - { - AnsiConsole.MarkupLine("[red]Entry not found.[/]"); - } - } - else - { - AnsiConsole.MarkupLine("[red]Invalid ID format.[/]"); - } - - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - } - } - - public static string EmailInput() - { - string email; - while (true) - { - Console.WriteLine("Enter email address:"); - email = Console.ReadLine(); - - if (email.Contains("@") && email.Contains(".")) - { - break; - } - else - { - AnsiConsole.MarkupLine("[red]Invalid email format. Please try again.[/]"); - } - } - - return email; - } - - public static string PhoneNumberInput() - { - string phoneNumber; - while (true) - { - Console.WriteLine("Enter phone number (must be 11 digits):"); - phoneNumber = Console.ReadLine(); - - if (phoneNumber.Length == 11 && long.TryParse(phoneNumber, out _)) - { - break; - } - else - { - AnsiConsole.MarkupLine("[red]Invalid phone number. Please enter exactly 11 digits.[/]"); - } - } - - return phoneNumber; + UserInterface.UserInterface.Menu(); } enum FileTypes From bfb48bb4a3c29bf2a4d47e43d3b299da08d1c89f Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 15:20:28 +0100 Subject: [PATCH 22/42] Added userInterface class --- .../UserInterface/UserInterface.cs | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs new file mode 100644 index 0000000..8559da7 --- /dev/null +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs @@ -0,0 +1,202 @@ +using DocumentProcessor.JJHH17.Models; +using Spectre.Console; + +namespace DocumentProcessor.JJHH17.UserInterface; + +public class UserInterface +{ + enum MenuOptions + { + AddEntry, + Read, + Delete, + Export, + Exit + } + + public static void Menu() + { + // Seeds data if database is empty + using (var context = new PhoneBookContext()) + { + if (!context.Phonebooks.Any()) + { + Program.SeedOption(); + } + } + + + bool running = true; + while (running) + { + AnsiConsole.MarkupLine("[bold yellow]Document Processor Menu[/]"); + Console.Clear(); + + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select an option:") + .AddChoices(Enum.GetValues())); + + switch (choice) + { + case MenuOptions.AddEntry: + Console.Clear(); + AddEntry(); + break; + + case MenuOptions.Read: + Console.Clear(); + ReadEntries(); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + break; + + case MenuOptions.Delete: + Console.Clear(); + DeleteEntry(); + break; + + case MenuOptions.Export: + Console.Clear(); + Program.CreateExportCSV(); + Console.WriteLine("Enter any key to return to the menu..."); + Console.ReadKey(); + break; + + case MenuOptions.Exit: + Console.WriteLine("Press any key to exit..."); + Console.ReadKey(); + running = false; + break; + } + } + } + + public static void AddEntry() + { + AnsiConsole.MarkupLine("[bold yellow]Add Entry[/]"); + Console.WriteLine("Enter name:"); + string name = Console.ReadLine(); + string email = EmailInput(); + string phoneNumber = PhoneNumberInput(); + + using (var context = new PhoneBookContext()) + { + var newEntry = new Phonebook + { + Name = name, + Email = email, + PhoneNumber = phoneNumber + }; + + context.Phonebooks.Add(newEntry); + context.SaveChanges(); + } + + AnsiConsole.MarkupLine("[green]Entry added successfully![/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + + public static void ReadEntries() + { + using (var context = new PhoneBookContext()) + { + var query = context.Phonebooks.ToList(); + + if (query.Count == 0) + { + AnsiConsole.MarkupLine("[yellow]No entries were found[/]"); + } + else + { + var table = new Table(); + table.AddColumn("ID"); + table.AddColumn("Name"); + table.AddColumn("Email"); + table.AddColumn("Phone Number"); + + foreach (var entry in query) + { + table.AddRow(entry.Id.ToString(), entry.Name, entry.Email, entry.PhoneNumber); + } + AnsiConsole.Write(table); + } + } + } + + public static void DeleteEntry() + { + using (var context = new PhoneBookContext()) + { + AnsiConsole.MarkupLine("[bold yellow]Delete Entry[/]"); + ReadEntries(); + Console.WriteLine("Enter the ID of the entry to delete:"); + + if (int.TryParse(Console.ReadLine(), out int id)) + { + var entry = context.Phonebooks.Find(id); + if (entry != null) + { + context.Phonebooks.Remove(entry); + context.SaveChanges(); + AnsiConsole.MarkupLine("[green]Entry deleted successfully![/]"); + } + else + { + AnsiConsole.MarkupLine("[red]Entry not found.[/]"); + } + } + else + { + AnsiConsole.MarkupLine("[red]Invalid ID format.[/]"); + } + + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + } + + public static string EmailInput() + { + string email; + while (true) + { + Console.WriteLine("Enter email address:"); + email = Console.ReadLine(); + + if (email.Contains("@") && email.Contains(".")) + { + break; + } + else + { + AnsiConsole.MarkupLine("[red]Invalid email format. Please try again.[/]"); + } + } + + return email; + } + + public static string PhoneNumberInput() + { + string phoneNumber; + while (true) + { + Console.WriteLine("Enter phone number (must be 11 digits):"); + phoneNumber = Console.ReadLine(); + + if (phoneNumber.Length == 11 && long.TryParse(phoneNumber, out _)) + { + break; + } + else + { + AnsiConsole.MarkupLine("[red]Invalid phone number. Please enter exactly 11 digits.[/]"); + } + } + + return phoneNumber; + } + +} \ No newline at end of file From 912ea61b6ccf04c9ae8f368308b3de7092e185f3 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 15:36:31 +0100 Subject: [PATCH 23/42] added connection to dataseeding class --- .../DocumentProcessor.JJHH17/UserInterface/UserInterface.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs index 8559da7..84d6c44 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs @@ -1,5 +1,6 @@ using DocumentProcessor.JJHH17.Models; using Spectre.Console; +using Document.Processor.JJHH17.DataSeeding; namespace DocumentProcessor.JJHH17.UserInterface; @@ -21,7 +22,7 @@ public static void Menu() { if (!context.Phonebooks.Any()) { - Program.SeedOption(); + DataSeeding.SeedOption(); } } From efb7376a1fa24c2656f6f9ba4806f6200e2e380b Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 15:38:06 +0100 Subject: [PATCH 24/42] Moved dataseeding mechanisms into their own class --- .../DataSeeding/DataSeeding.cs | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs new file mode 100644 index 0000000..1848a5f --- /dev/null +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs @@ -0,0 +1,190 @@ +using DocumentProcessor.JJHH17.Models; +using ExcelDataReader; +using Spectre.Console; +using System.Data; + +namespace Document.Processor.JJHH17.DataSeeding; + +public class DataSeeding +{ + enum FileTypes + { + CSV, + XLS, + XLSX + } + + public static void SeedOption() + { + Console.Clear(); + AnsiConsole.MarkupLine("[bold yellow]Seed Database via a given file type[/]"); + + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select a file type to import from:") + .AddChoices(Enum.GetValues())); + + switch (choice) + { + case FileTypes.CSV: + SeedCSVData(); + break; + case FileTypes.XLS: + SeedXLSData(); + break; + case FileTypes.XLSX: + SeedXLSXData(); + break; + } + } + + public static List ReadFile(string filePath) + { + List rows = new List(); + + try + { + string[] lines = File.ReadAllLines(filePath); + + foreach (var line in lines) + { + var values = line.Split(','); + rows.Add(values); + } + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error reading file: {ex.Message}[/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + + return rows; + } + + public static List ReadExcel(string filePath) + { + System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); + + using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read); + using var reader = ExcelReaderFactory.CreateReader(stream); + + var dataset = reader.AsDataSet(new ExcelDataSetConfiguration + { + ConfigureDataTable = _ => new ExcelDataTableConfiguration + { + UseHeaderRow = true + } + }); + + var table = dataset.Tables[0]; + var rows = new List(); + + foreach (DataRow dr in table.Rows) + { + var name = dr["Name"].ToString()?.Trim(); + var email = dr["Email"].ToString()?.Trim(); + var phoneNumber = dr["PhoneNumber"].ToString()?.Trim(); + + rows.Add(new string[] { name, email, phoneNumber }); + } + + return rows; + } + + public static void SeedCSVData() + { + try + { + string csvFilePath = "Import Data - Sheet1.csv"; + List csvData = ReadFile(csvFilePath); + + foreach (string[] row in csvData) + { + using (var context = new PhoneBookContext()) + { + var newEntry = new Phonebook + { + Name = row[0], + Email = row[1], + PhoneNumber = row[2] + }; + context.Phonebooks.Add(newEntry); + context.SaveChanges(); + } + } + + AnsiConsole.MarkupLine("[green]Database seeded successfully from CSV![/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error reading CSV file: {ex.Message}[/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + } + + public static void SeedXLSData() + { + try + { + string xlsFilePath = "Import Data - Sheet1.xls"; + var xlsData = ReadExcel(xlsFilePath); + using var context = new PhoneBookContext(); + foreach (var row in xlsData) + { + var newEntry = new Phonebook + { + Name = row[0], + Email = row[1], + PhoneNumber = row[2] + }; + context.Phonebooks.Add(newEntry); + } + context.SaveChanges(); + AnsiConsole.MarkupLine("[green]Database seeded successfully from XLS![/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error reading XLS file: {ex.Message}[/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + } + + public static void SeedXLSXData() + { + try + { + string xlsxFilePath = "Import Data - Sheet1.xlsx"; + var xlsxData = ReadExcel(xlsxFilePath); + + using var context = new PhoneBookContext(); + foreach (var row in xlsxData) + { + var newEntry = new Phonebook + { + Name = row[0], + Email = row[1], + PhoneNumber = row[2] + }; + context.Phonebooks.Add(newEntry); + } + + context.SaveChanges(); + AnsiConsole.MarkupLine("[green]Database seeded successfully from XLSX![/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error reading XLSX file: {ex.Message}[/]"); + Console.WriteLine("Press any key to return to the menu..."); + Console.ReadKey(); + } + } +} \ No newline at end of file From 7651931964c314911d8fdb4279900c5fb7084b0f Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 16:06:23 +0100 Subject: [PATCH 25/42] Moved data seeding mechanism into its own class --- .../DocumentProcessor.JJHH17/Program.cs | 196 ------------------ 1 file changed, 196 deletions(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index 931429a..ea67afe 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -13,200 +13,4 @@ public static void Main(string[] args) { UserInterface.UserInterface.Menu(); } - - enum FileTypes - { - CSV, - XLS, - XLSX - } - - public static void SeedOption() - { - Console.Clear(); - AnsiConsole.MarkupLine("[bold yellow]Seed Database via a given file type[/]"); - - var choice = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("Select a file type to import from:") - .AddChoices(Enum.GetValues())); - - switch (choice) - { - case FileTypes.CSV: - SeedCSVData(); - break; - case FileTypes.XLS: - SeedXLSData(); - break; - case FileTypes.XLSX: - SeedXLSXData(); - break; - } - } - - public static List ReadFile(string filePath) - { - List rows = new List(); - - try - { - string[] lines = File.ReadAllLines(filePath); - - foreach (var line in lines) - { - var values = line.Split(','); - rows.Add(values); - } - } - catch (Exception ex) - { - AnsiConsole.MarkupLine($"[red]Error reading file: {ex.Message}[/]"); - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - } - - return rows; - } - - public static List ReadExcel(string filePath) - { - System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); - - using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read); - using var reader = ExcelReaderFactory.CreateReader(stream); - - var dataset = reader.AsDataSet(new ExcelDataSetConfiguration - { - ConfigureDataTable = _ => new ExcelDataTableConfiguration - { - UseHeaderRow = true - } - }); - - var table = dataset.Tables[0]; - var rows = new List(); - - foreach (DataRow dr in table.Rows) - { - var name = dr["Name"].ToString()?.Trim(); - var email = dr["Email"].ToString()?.Trim(); - var phoneNumber = dr["PhoneNumber"].ToString()?.Trim(); - - rows.Add(new string[] { name, email, phoneNumber }); - } - - return rows; - } - - public static void SeedCSVData() - { - try - { - string csvFilePath = "Import Data - Sheet1.csv"; - List csvData = ReadFile(csvFilePath); - - foreach (string[] row in csvData) - { - using (var context = new PhoneBookContext()) - { - var newEntry = new Phonebook - { - Name = row[0], - Email = row[1], - PhoneNumber = row[2] - }; - context.Phonebooks.Add(newEntry); - context.SaveChanges(); - } - } - - AnsiConsole.MarkupLine("[green]Database seeded successfully from CSV![/]"); - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - } - catch (Exception ex) - { - AnsiConsole.MarkupLine($"[red]Error reading CSV file: {ex.Message}[/]"); - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - } - } - - public static void SeedXLSData() - { - try - { - string xlsFilePath = "Import Data - Sheet1.xls"; - var xlsData = ReadExcel(xlsFilePath); - using var context = new PhoneBookContext(); - foreach (var row in xlsData) - { - var newEntry = new Phonebook - { - Name = row[0], - Email = row[1], - PhoneNumber = row[2] - }; - context.Phonebooks.Add(newEntry); - } - context.SaveChanges(); - AnsiConsole.MarkupLine("[green]Database seeded successfully from XLS![/]"); - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - } - catch (Exception ex) - { - AnsiConsole.MarkupLine($"[red]Error reading XLS file: {ex.Message}[/]"); - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - } - } - - public static void SeedXLSXData() - { - try - { - string xlsxFilePath = "Import Data - Sheet1.xlsx"; - var xlsxData = ReadExcel(xlsxFilePath); - - using var context = new PhoneBookContext(); - foreach (var row in xlsxData) - { - var newEntry = new Phonebook - { - Name = row[0], - Email = row[1], - PhoneNumber = row[2] - }; - context.Phonebooks.Add(newEntry); - } - - context.SaveChanges(); - AnsiConsole.MarkupLine("[green]Database seeded successfully from XLSX![/]"); - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - } - catch (Exception ex) - { - AnsiConsole.MarkupLine($"[red]Error reading XLSX file: {ex.Message}[/]"); - Console.WriteLine("Press any key to return to the menu..."); - Console.ReadKey(); - } - } - - public static void CreateExportCSV() - { - using (var context = new PhoneBookContext()) - { - var entries = context.Phonebooks.ToList(); - using (var writer = new StreamWriter("ExportedPhonebook.pdf")) - using (var csv = new CsvWriter(writer, System.Globalization.CultureInfo.InvariantCulture)) - { - csv.WriteRecords(entries); - } - } - AnsiConsole.MarkupLine("[green]Data exported to ExportedPhonebook.pdf successfully![/]"); - AnsiConsole.MarkupLine("[green]You can find the CSV in 'CodeReviews.Console.DocumentProcessor\\DocumentProcessor.JJHH17\\DocumentProcessor.JJHH17\\bin\\Debug\\net8.0\\ExportedPhonebook.pdf\'[/]"); - } } \ No newline at end of file From f4d4031ecacb39c6647cb1d405f3e9cbcd611343 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 16:07:32 +0100 Subject: [PATCH 26/42] Added data seeding into its own class --- .../DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs index 1848a5f..eee1771 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs @@ -5,7 +5,7 @@ namespace Document.Processor.JJHH17.DataSeeding; -public class DataSeeding +public class DataSeed { enum FileTypes { From 33393293b27d7f8d3d7c84b01d354d81f3b57a4a Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 16:07:51 +0100 Subject: [PATCH 27/42] Added data export to its own class --- .../DataExporting/DataExport.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs new file mode 100644 index 0000000..4ab9250 --- /dev/null +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs @@ -0,0 +1,23 @@ +using CsvHelper; +using DocumentProcessor.JJHH17.Models; +using Spectre.Console; + +namespace Document.Processor.JJHH17.DataExporting; + +public class DataExport +{ + public static void CreateExportCSV() + { + using (var context = new PhoneBookContext()) + { + var entries = context.Phonebooks.ToList(); + using (var writer = new StreamWriter("ExportedPhonebook.pdf")) + using (var csv = new CsvWriter(writer, System.Globalization.CultureInfo.InvariantCulture)) + { + csv.WriteRecords(entries); + } + } + AnsiConsole.MarkupLine("[green]Data exported to ExportedPhonebook.pdf successfully![/]"); + AnsiConsole.MarkupLine("[green]You can find the CSV in 'CodeReviews.Console.DocumentProcessor\\DocumentProcessor.JJHH17\\DocumentProcessor.JJHH17\\bin\\Debug\\net8.0\\ExportedPhonebook.pdf\'[/]"); + } +} \ No newline at end of file From 1ed5b7a2aef3a1c077d865d6b68de18a267b5954 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 16:08:04 +0100 Subject: [PATCH 28/42] Added support for exporting PDF --- .../DocumentProcessor.JJHH17/UserInterface/UserInterface.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs index 84d6c44..5cac7c6 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs @@ -1,6 +1,7 @@ using DocumentProcessor.JJHH17.Models; using Spectre.Console; using Document.Processor.JJHH17.DataSeeding; +using Document.Processor.JJHH17.DataExporting; namespace DocumentProcessor.JJHH17.UserInterface; @@ -22,7 +23,7 @@ public static void Menu() { if (!context.Phonebooks.Any()) { - DataSeeding.SeedOption(); + DataSeed.SeedOption(); } } @@ -59,7 +60,7 @@ public static void Menu() case MenuOptions.Export: Console.Clear(); - Program.CreateExportCSV(); + DataExport.CreateExportCSV(); Console.WriteLine("Enter any key to return to the menu..."); Console.ReadKey(); break; From 26494ab43c7a7d6d6192118a6d01ec5a479df832 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 16:24:46 +0100 Subject: [PATCH 29/42] Added reference to export menu --- .../DocumentProcessor.JJHH17/UserInterface/UserInterface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs index 5cac7c6..b535e90 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs @@ -60,7 +60,7 @@ public static void Menu() case MenuOptions.Export: Console.Clear(); - DataExport.CreateExportCSV(); + DataExport.ExportMenu(); Console.WriteLine("Enter any key to return to the menu..."); Console.ReadKey(); break; From 5c9def77c1cda908eaed6f075b3077165f88092d Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 16:44:18 +0100 Subject: [PATCH 30/42] Added ability to export to CSV --- .../DataExporting/DataExport.cs | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs index 4ab9250..e696075 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs @@ -6,7 +6,34 @@ namespace Document.Processor.JJHH17.DataExporting; public class DataExport { - public static void CreateExportCSV() + enum ExportMenuOptions + { + PDF, + CSV + } + + public static void ExportMenu() + { + Console.Clear(); + + AnsiConsole.MarkupLine("[bold yellow]Export Data to a given file type[/]"); + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select a file type to export to:") + .AddChoices(Enum.GetValues())); + switch (choice) + { + case ExportMenuOptions.PDF: + CreateExportPDF(); + break; + + case ExportMenuOptions.CSV: + CreateExportCsv(); + break; + } + } + + public static void CreateExportPDF() { using (var context = new PhoneBookContext()) { @@ -20,4 +47,20 @@ public static void CreateExportCSV() AnsiConsole.MarkupLine("[green]Data exported to ExportedPhonebook.pdf successfully![/]"); AnsiConsole.MarkupLine("[green]You can find the CSV in 'CodeReviews.Console.DocumentProcessor\\DocumentProcessor.JJHH17\\DocumentProcessor.JJHH17\\bin\\Debug\\net8.0\\ExportedPhonebook.pdf\'[/]"); } + + public static void CreateExportCsv() + { + using (var context = new PhoneBookContext()) + { + var entries = context.Phonebooks.ToList(); + using (var writer = new StreamWriter("ExportedPhonebook.csv")) + using (var csv = new CsvWriter(writer, System.Globalization.CultureInfo.InvariantCulture)) + { + csv.WriteRecords(entries); + } + } + + AnsiConsole.MarkupLine("[green]Data exported to ExportedPhonebook.csv successfully![/]"); + AnsiConsole.MarkupLine("[green]You can find the CSV in 'CodeReviews.Console.DocumentProcessor\\DocumentProcessor.JJHH17\\DocumentProcessor.JJHH17\\bin\\Debug\\net8.0\\ExportedPhonebook.csv\'[/]"); + } } \ No newline at end of file From 745737914ff6b8143fa2d0c85e026bb8c3a97a0c Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 20:12:05 +0100 Subject: [PATCH 31/42] Installed Cronos package, for cron/scheduling tasks --- .../DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj index 404e5b2..439e3f5 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj @@ -8,6 +8,7 @@ + From 50794e33b83faba1d8ac4dda5cbf60224f62f6bc Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 20:52:36 +0100 Subject: [PATCH 32/42] Added code to export data to a csv every day at 9am, using the cronos package --- .../ScheduledExport/ScheduledExport.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/ScheduledExport/ScheduledExport.cs diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/ScheduledExport/ScheduledExport.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/ScheduledExport/ScheduledExport.cs new file mode 100644 index 0000000..1959916 --- /dev/null +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/ScheduledExport/ScheduledExport.cs @@ -0,0 +1,61 @@ +using Cronos; +using Document.Processor.JJHH17.DataExporting; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace DocumentProcessor.JJHH17.ScheduledExport; + +public class ScheduledExport +{ + +} + +public class ScheduledExportJob : BackgroundService +{ + private static string TimeZoneID = "Europe/London"; + + // Scheduled to run every day, 09:00 AM (BST / GMT) + private static readonly CronExpression cron = CronExpression.Parse("0 9 * * *"); + private static readonly TimeZoneInfo Tz = TimeZoneInfo.FindSystemTimeZoneById(TimeZoneID); + private readonly ILogger _logger; + + public ScheduledExportJob(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stopToken) + { + _logger.LogInformation("PDF Export is generating"); + + while (!stopToken.IsCancellationRequested) + { + var next = cron.GetNextOccurrence(DateTimeOffset.Now, Tz); + if (next is null) break; + + var delay = next.Value - DateTimeOffset.Now; + _logger.LogInformation($"Next PDF Export scheduled at {next})", next.Value); + + try + { + await Task.Delay(delay, stopToken); + } + catch (TaskCanceledException) + { + break; + } + + try + { + DataExport.CreateExportPDF(); + _logger.LogInformation("PDF Export generated successfully"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occurred while generating PDF Export"); + } + } + + _logger.LogInformation("Scheduled PDF Export is stopping"); + } +} \ No newline at end of file From 05f886d5ea8e89884ab326ccdaab13058521ba0c Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 20:52:50 +0100 Subject: [PATCH 33/42] Added cronos package for report scheduling --- .../DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj index 439e3f5..b7a1821 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj @@ -17,6 +17,9 @@ all + + + From 1a3e7e42fd975709c53af48a25732041b35fb0b8 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Fri, 17 Oct 2025 20:53:19 +0100 Subject: [PATCH 34/42] Report automated generation now runs concurrently to the app UI --- .../DocumentProcessor.JJHH17/Program.cs | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs index ea67afe..5150eaf 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs @@ -1,16 +1,35 @@ -using DocumentProcessor.JJHH17.UserInterface; -using DocumentProcessor.JJHH17.Models; -using Spectre.Console; -using CsvHelper; -using System.Data; -using ExcelDataReader; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace DocumentProcessor.JJHH17; public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { - UserInterface.UserInterface.Menu(); + var builder = Host.CreateApplicationBuilder(args); + builder.Services.AddHostedService(); + builder.Services.AddLogging(); + + using var host = builder.Build(); + var runHost = host.RunAsync(); + + var uiCts = new CancellationTokenSource(); + var uiTask = Task.Run(() => UserInterface.UserInterface.Menu(), uiCts.Token); + + await Task.WhenAny(runHost, uiTask); + + if (uiTask.IsCompleted) + { + await host.StopAsync(); + } + else + { + uiCts.Cancel(); + try { await uiTask; } + catch (OperationCanceledException) { } + + await runHost; + } } } \ No newline at end of file From 53d34c27b502056b464224ce16e2be3cba212e6a Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 21 Oct 2025 20:43:33 +0100 Subject: [PATCH 35/42] Added reference to azure blob connection string --- .../DocumentProcessor.JJHH17/Configurations/App.config | 1 + 1 file changed, 1 insertion(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config index 173209e..ecf469e 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config @@ -3,5 +3,6 @@ + \ No newline at end of file From 04f38e15255ea9cff3b572a2c2709ab2a21e10b9 Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 21 Oct 2025 20:47:49 +0100 Subject: [PATCH 36/42] Added azure blob package --- .../DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj index b7a1821..50bb9be 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj @@ -8,6 +8,7 @@ + From 2c8982dc937491cac2cc8e347801e5469801fb1b Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 21 Oct 2025 20:53:16 +0100 Subject: [PATCH 37/42] Added container name reference to app.config --- .../DocumentProcessor.JJHH17/Configurations/App.config | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config index ecf469e..066adb6 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config @@ -3,6 +3,7 @@ - + + \ No newline at end of file From 472022f17aa924d73f01f63cbd89f2b535247a8c Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Tue, 21 Oct 2025 21:20:13 +0100 Subject: [PATCH 38/42] Added code to store to an Azure blob --- .../DataExporting/DataExport.cs | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs index e696075..a00d55b 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs @@ -1,6 +1,10 @@ using CsvHelper; +using DocumentProcessor.JJHH17.DataExporting; using DocumentProcessor.JJHH17.Models; using Spectre.Console; +using Azure.Storage.Blobs; +using System.Configuration; +using Azure.Storage.Blobs.Models; namespace Document.Processor.JJHH17.DataExporting; @@ -9,7 +13,8 @@ public class DataExport enum ExportMenuOptions { PDF, - CSV + CSV, + AzureBlobStorage } public static void ExportMenu() @@ -30,6 +35,10 @@ public static void ExportMenu() case ExportMenuOptions.CSV: CreateExportCsv(); break; + + case ExportMenuOptions.AzureBlobStorage: + CreateAzureBlobExport(); + break; } } @@ -63,4 +72,38 @@ public static void CreateExportCsv() AnsiConsole.MarkupLine("[green]Data exported to ExportedPhonebook.csv successfully![/]"); AnsiConsole.MarkupLine("[green]You can find the CSV in 'CodeReviews.Console.DocumentProcessor\\DocumentProcessor.JJHH17\\DocumentProcessor.JJHH17\\bin\\Debug\\net8.0\\ExportedPhonebook.csv\'[/]"); } + + public static async Task CreateAzureBlobExport() + { + var localCsv = "ExportedPhonebook.csv"; + if (!File.Exists(localCsv)) + { + AnsiConsole.MarkupLine("[yellow]CSV file not found. Creating CSV file first...[/]"); + CreateExportCsv(); + } + + var connectionString = ConfigurationManager.AppSettings["AzureBlobConnectionString"]; + var containerName = ConfigurationManager.AppSettings["ContainerName"]; + + if (string.IsNullOrEmpty(connectionString) || string.IsNullOrEmpty(containerName)) + { + AnsiConsole.MarkupLine("[red]Azure Blob Storage connection string or container name is not configured properly. Please check in the app.config file.[/]"); + return; + } + + var blobServiceClient = new BlobServiceClient(connectionString); + var containerClient = blobServiceClient.GetBlobContainerClient(containerName); + await containerClient.CreateIfNotExistsAsync(); + + var blobClient = containerClient.GetBlobClient("ExportedPhonebook.csv"); + using var fileStream = File.OpenRead(localCsv); + + var options = new BlobUploadOptions + { + HttpHeaders = new BlobHttpHeaders { ContentType = "text/csv" } + }; + + await blobClient.UploadAsync(fileStream, options); + AnsiConsole.MarkupLine("[green]CSV file uploaded to Azure Blob Storage successfully![/]"); + } } \ No newline at end of file From 07f8898b10f75acf4de9f27e5e48635dce2f4d0e Mon Sep 17 00:00:00 2001 From: James Hatfield Date: Thu, 23 Oct 2025 19:53:47 +0100 Subject: [PATCH 39/42] Removed unused using call in dataexport class --- .../DocumentProcessor.JJHH17/DataExporting/DataExport.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs index a00d55b..bfb2198 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs @@ -1,5 +1,4 @@ using CsvHelper; -using DocumentProcessor.JJHH17.DataExporting; using DocumentProcessor.JJHH17.Models; using Spectre.Console; using Azure.Storage.Blobs; From f72ecb58171c617ed3c0a6955c13c50476efe2dd Mon Sep 17 00:00:00 2001 From: JJHH17 Date: Thu, 23 Oct 2025 20:03:16 +0100 Subject: [PATCH 40/42] Create README for Document Processor Application Added initial README with project overview. --- DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md new file mode 100644 index 0000000..83856ca --- /dev/null +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md @@ -0,0 +1,2 @@ +# Document Processor Application - PhoneBook Tracker +A phonebook tracker application, allowing the user to seed and export data via local files, as well as via Azure Blob storage. From 28afb0959aacd4f9472049b8a20d83ae6f1399fe Mon Sep 17 00:00:00 2001 From: JJHH17 Date: Thu, 23 Oct 2025 20:51:33 +0100 Subject: [PATCH 41/42] Enhance README with project details and usage instructions Expanded README.md to include detailed project overview, usage steps, and file import/export instructions. --- .../DocumentProcessor.JJHH17/README.md | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md index 83856ca..6879a4f 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md @@ -1,2 +1,90 @@ # Document Processor Application - PhoneBook Tracker A phonebook tracker application, allowing the user to seed and export data via local files, as well as via Azure Blob storage. + +## Project Overview +This is a project that: +- Allows the user to create and store phonebook user details. +- The user can seed data from an XLS, XLSX or CSV File (local file). +- The user can export the databases items into a CSV, XLSX or CSV file, as well as exporting to an Azure Blob Storage instance. +- The data seeding element of the application will only be presented to the user if the database contains 0 elements upon startup. +- Import and Export local files are stored in the following directory: + +```JJHH17\bin\Debug\net8.0\``` + +## Technologies Used +- SQL Server (Local DB Instance) for database storage +- Entity Framework Core +- Spectre Console (for UI / Console navigation) +- Azure Blob Storage +- Excel Data Reader (package) +- CSV Helper (CSV Writing Package) +- Configuration Manager + +## Usage Steps +### Creating an SQL Server Local DB Instance +- This project uses a Local DB instance of SQL Server, meaning a Database file will need to be created in order for the program to run +- You can create a Local DB instance by running the following terminal command, with SQL Server installed: + +```sqllocaldb create documentProcessor``` + +### Connection strings to Local DB +- Connection strings are managed via the "app.config" file. +- Keep the "Server" string as "localdb". +- Change the "Database Name" string to whatever value you name your local db instance as. +- These values will then be appended to the full database connection string. + +### Creating and using the application +- Clone the application and open it in your IDE of choice. +- Run the migrations command for Entity Framework - this is done via the following commands: + +```dotnet ef migrations add InitialCreate``` + +then + +```dotnet ef database update``` + +This will create the relevant EF tables. + +### File Importing +- The app allows users to import data via CSV, XLSX or XLS files. +If the apps database instance is empty when the app is started, the user is prompted to import data via the selected file type: + +image + +All Files must be stored within the following directory, with the following naming conventions: + +```JJHH17\bin\Debug\net8.0\``` + +- For CSV: Import Data - Sheet1.csv +- For XLSX: Import Data - Sheet1.xlsx +- For XLS: Import Data - Sheet1.xls + +### Import File Formats: +In order for data to be imported correctly, they must be in the correct format: + +CSV: +- Name,Email Address,Telephone Number + +XLSX and XLS: +These must contain the following cell headers: +Name EmailAddress TelephoneNumber + +### File Exporting + +image + +- All files exported can be located in the following location, inside of the projects directory: +```JJHH17\bin\Debug\net8.0\``` + +- Users can export as a CSV, PDF, or route the export to an Azure Blob Storage instance (more details on this found below). + +### Azure Blob Storage Connection +For data exporting, users can export the file to an Azure Blob instance. + +All connection strings can be located and adjusted in the App.Config file of the project. + +The main requirements here are: +- The connection string - This can be gathered when a Blob container has been created. +- The container name - This is the blob container name where you will store the file. + +Once these values have been added, any data found in the Database will be exported to your blob instance. From 7d15b0c289ad25ee093fc3601ba41c8e1f0915a0 Mon Sep 17 00:00:00 2001 From: JJHH17 Date: Thu, 23 Oct 2025 20:56:06 +0100 Subject: [PATCH 42/42] Update README with automated file export details Added information about automated file export using CRONOS package. --- .../DocumentProcessor.JJHH17/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md index 6879a4f..95072d2 100644 --- a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md +++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md @@ -7,6 +7,7 @@ This is a project that: - The user can seed data from an XLS, XLSX or CSV File (local file). - The user can export the databases items into a CSV, XLSX or CSV file, as well as exporting to an Azure Blob Storage instance. - The data seeding element of the application will only be presented to the user if the database contains 0 elements upon startup. +- Finally, we also run an export of data based on a scheduled task, which by default is executed daily at 09:00 am (BST / GMT UK time). - Import and Export local files are stored in the following directory: ```JJHH17\bin\Debug\net8.0\``` @@ -19,6 +20,7 @@ This is a project that: - Excel Data Reader (package) - CSV Helper (CSV Writing Package) - Configuration Manager +- CRONOS package - For running the file export on an automated basis. ## Usage Steps ### Creating an SQL Server Local DB Instance @@ -78,6 +80,11 @@ Name EmailAddress TelephoneNumber - Users can export as a CSV, PDF, or route the export to an Azure Blob Storage instance (more details on this found below). +### Automated File Export +- We use the CRONOS package for automating the export of the file export to a CSV file. +- This runs daily at 9am (BST / GMT (UK local time)). +- The class for this work can be found inside of the "Scheduled Export" folder. + ### Azure Blob Storage Connection For data exporting, users can export the file to an Azure Blob instance.