diff --git a/docs/errata/README.md b/docs/errata/README.md
index a4d6a28..3205735 100644
--- a/docs/errata/README.md
+++ b/docs/errata/README.md
@@ -4,7 +4,7 @@ If you find any mistakes in the seventh edition, *C# 11 and .NET 7 - Modern Cros
[**Errata** (47 items)](errata.md): Typos, tool user interface changes, or mistakes in code that would cause a compilation error that prevents a successful build.
-[**Improvements** (42 items)](improvements.md): Changes to text or code that would improve the content. These are optional.
+[**Improvements** (43 items)](improvements.md): Changes to text or code that would improve the content. These are optional.
[**Common Errors** (6 items)](common-errors.md): These are some of the most common errors that a reader might encounter when trying to get code in book tasks to work, or when trying to write your own code.
diff --git a/docs/errata/improvements.md b/docs/errata/improvements.md
index 7c428ab..b21c5b3 100644
--- a/docs/errata/improvements.md
+++ b/docs/errata/improvements.md
@@ -1,4 +1,4 @@
-**Improvements** (42 items)
+**Improvements** (43 items)
If you have suggestions for improvements, then please [raise an issue in this repository](https://github.com/markjprice/cs11dotnet7/issues) or email me at markjprice (at) gmail.com.
@@ -28,6 +28,7 @@ If you have suggestions for improvements, then please [raise an issue in this re
- [Page 339 - Viewing source links with Visual Studio 2022](#page-339---viewing-source-links-with-visual-studio-2022)
- [Page 343 - Packaging a library for NuGet](#page-343---packaging-a-library-for-nuget)
- [Page 351 - Using non-.NET Standard libraries](#page-351---using-non-net-standard-libraries)
+- [Page 369 - Activating regular expression syntax coloring](#page-369---activating-regular-expression-syntax-coloring)
- [Page 378 - Dictionaries](#page-378---dictionaries)
- [Page 444 - Connecting to a database](#page-444---connecting-to-a-database)
- [Page 453 - Scaffolding models using an existing database](#page-453---scaffolding-models-using-an-existing-database)
@@ -706,6 +707,17 @@ for (int i = 0; i < matrix.Axes[1].Points.Length; i++)
}
```
+# Page 369 - Activating regular expression syntax coloring
+
+In Step 2, I wrote, "Right-click in the `StringSyntax` attribute, select **Go To Implementation**, and note..."
+
+Visual Studio 2022 has two similar features:
+
+- **Go To Definition** *F12*: Should go to the decompiled metadata for a member or type. But if you have previously viewed source link, then it goes to source link!
+- **Go To Implementation** *Ctrl* + *F12*: Should go to the source link implementation for a member or type. But if you have disabled source link, then it goes to the decompiled metadata.
+
+In the next edition, I will add a note about this.
+
# Page 378 - Dictionaries
In the next edition, I will add a note at the bottom of this section to set an expectation that readers will come across dictionaries again later in the book in more practical ways.
diff --git a/vs4win/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml b/vs4win/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml
new file mode 100644
index 0000000..d2591f9
--- /dev/null
+++ b/vs4win/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml
@@ -0,0 +1,32 @@
+@page
+@using Northwind.Web.Pages
+@using Packt.Shared
+@model CustomerOrdersModel
+@{
+ string title = "Customer and their orders";
+ ViewData["Title"] = $"Northwind B2B - {title}";
+}
+
+
@title
+
+ @if (Model.Customer is not null)
+ {
+
+
@Model.Customer.CompanyName
+
+
+
+
+ | Order Id | Order Date |
+
+
+ @foreach (Order o in Model.Customer.Orders)
+ {
+ | @o.OrderId | @o.OrderDate |
+ }
+
+
+
+ }
+
+
\ No newline at end of file
diff --git a/vs4win/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml.cs b/vs4win/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml.cs
new file mode 100644
index 0000000..a73fc74
--- /dev/null
+++ b/vs4win/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml.cs
@@ -0,0 +1,25 @@
+using Microsoft.AspNetCore.Mvc.RazorPages; // PageModel
+using Microsoft.EntityFrameworkCore; // Include extension method
+using Packt.Shared; // Customer
+
+namespace Northwind.Web.Pages;
+
+public class CustomerOrdersModel : PageModel
+{
+ public Customer? Customer;
+
+ private NorthwindContext db;
+
+ public CustomerOrdersModel(NorthwindContext db)
+ {
+ this.db = db;
+ }
+
+ public void OnGet()
+ {
+ string id = HttpContext.Request.Query["id"];
+
+ Customer = db.Customers.Include(c => c.Orders)
+ .SingleOrDefault(c => c.CustomerId == id);
+ }
+}
diff --git a/vs4win/PracticalApps/Northwind.Web/Pages/Customers.cshtml b/vs4win/PracticalApps/Northwind.Web/Pages/Customers.cshtml
new file mode 100644
index 0000000..f33a4f0
--- /dev/null
+++ b/vs4win/PracticalApps/Northwind.Web/Pages/Customers.cshtml
@@ -0,0 +1,40 @@
+@page
+@using Northwind.Web.Pages
+@using Packt.Shared
+@model CustomersModel
+@{
+ string title = "Customers by Country";
+ ViewData["Title"] = $"Northwind B2B - {title}";
+}
+
+
@title
+
+
Exercise 14.2 – Practice building a data-driven web page
+
+
+ @if (Model.CustomersByCountry is not null)
+ {
+ @foreach (IGrouping
cbc in Model.CustomersByCountry)
+ {
+
+ }
+ }
+
+
\ No newline at end of file
diff --git a/vs4win/PracticalApps/Northwind.Web/Pages/Customers.cshtml.cs b/vs4win/PracticalApps/Northwind.Web/Pages/Customers.cshtml.cs
new file mode 100644
index 0000000..03a1f5e
--- /dev/null
+++ b/vs4win/PracticalApps/Northwind.Web/Pages/Customers.cshtml.cs
@@ -0,0 +1,21 @@
+using Microsoft.AspNetCore.Mvc.RazorPages; // PageModel
+using Packt.Shared; // Customer
+
+namespace Northwind.Web.Pages;
+
+public class CustomersModel : PageModel
+{
+ public ILookup? CustomersByCountry;
+
+ private NorthwindContext db;
+
+ public CustomersModel(NorthwindContext db)
+ {
+ this.db = db;
+ }
+
+ public void OnGet()
+ {
+ CustomersByCountry = db.Customers.ToLookup(c => c.Country);
+ }
+}
diff --git a/vs4win/PracticalApps/Northwind.Web/Pages/Functions.cshtml b/vs4win/PracticalApps/Northwind.Web/Pages/Functions.cshtml
new file mode 100644
index 0000000..4c7e491
--- /dev/null
+++ b/vs4win/PracticalApps/Northwind.Web/Pages/Functions.cshtml
@@ -0,0 +1,190 @@
+@page
+@using Northwind.Web.Pages
+@using Packt.Shared
+@model FunctionsModel
+@{
+ string title = "Functions";
+ ViewData["Title"] = $"Northwind B2B - {title}";
+
+ string collapsedTimesTable = Model.TimesTableNumberInput.HasValue ? string.Empty : "collapse";
+ string collapsedCalculateTax = Model.Amount.HasValue ? string.Empty : "collapse";
+ string collapsedFactorial = Model.FactorialNumber.HasValue ? string.Empty : "collapse";
+ string collapsedFibonacci = Model.FibonacciNumber.HasValue ? string.Empty : "collapse";
+}
+
+
@title
+
+
Exercise 14.3 – Practice building web pages for console apps
+
Provide a web user interface to output times tables, calculate tax, and generate factorials and the Fibonacci sequence.
+
+
+
+
+
+
+
+ @if (Model.TimesTableNumberInput.HasValue)
+ {
+
+
+
@Model.TimesTableNumberInput times table
+ @for (int i = 1; i <= 12; i++)
+ {
+
+ @i x @Model.TimesTableNumberInput = @(i * Model.TimesTableNumberInput)
+
+ }
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+ @if (Model.Amount.HasValue)
+ {
+
+
+
You must pay @Model.TaxToPay in tax.
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+ @if (Model.FactorialNumber.HasValue)
+ {
+
+
+
@(Model.FactorialNumber)!
+
+ @(Model.FactorialNumber)! = @(Model.FactorialResult is null ? "null" : Model.FactorialResult.Value.ToString("N0"))
+
+
+
+
+ }
+ @if (Model.FactorialException is not null)
+ {
+
+
+
Exception
+
+ @Model.FactorialException.Message
+
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+ @if (Model.FibonacciNumber.HasValue)
+ {
+
+
+
Fibonacci term @Model.FibonacciNumber
+
+ Term @Model.FibonacciNumber of the fibonacci sequence = @(Model.FibonacciResult is null ? "null" : Model.FibonacciResult.Value.ToString("N0"))
+
+
+
+
+ }
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vs4win/PracticalApps/Northwind.Web/Pages/Functions.cshtml.cs b/vs4win/PracticalApps/Northwind.Web/Pages/Functions.cshtml.cs
new file mode 100644
index 0000000..bc42abe
--- /dev/null
+++ b/vs4win/PracticalApps/Northwind.Web/Pages/Functions.cshtml.cs
@@ -0,0 +1,141 @@
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace Northwind.Web.Pages;
+
+public class FunctionsModel : PageModel
+{
+ public int? TimesTableNumberInput { get; set; }
+
+ public decimal? Amount { get; set; }
+ public string? RegionCode { get; set; }
+ public decimal? TaxToPay { get; set; }
+
+ public int? FactorialNumber { get; set; }
+ public int? FactorialResult { get; set; }
+ public Exception? FactorialException { get; set; }
+
+ public int? FibonacciNumber { get; set; }
+ public int? FibonacciResult { get; set; }
+
+ public void OnGet()
+ {
+ // Times Table
+ if (int.TryParse(HttpContext.Request.Query["timesTableNumberInput"], out int i))
+ {
+ TimesTableNumberInput = i;
+ }
+
+ // Calculate Tax
+ if (decimal.TryParse(HttpContext.Request.Query["calculateTaxAmountInput"], out decimal amount))
+ {
+ Amount = amount;
+ RegionCode = HttpContext.Request.Query["calculateTaxRegionCodeInput"];
+ TaxToPay = CalculateTax(amount: amount, twoLetterRegionCode: RegionCode);
+ }
+
+ // Factorial
+ if (int.TryParse(HttpContext.Request.Query["factorialNumberInput"], out int fact))
+ {
+ FactorialNumber = fact;
+ try
+ {
+ FactorialResult = Factorial(fact);
+ }
+ catch (Exception ex)
+ {
+ FactorialException = ex;
+ }
+ }
+
+ // Fibonacci
+ if (int.TryParse(HttpContext.Request.Query["fibonacciNumberInput"], out int fib))
+ {
+ FibonacciNumber = fib;
+ FibonacciResult = FibImperative(term: fib);
+ }
+ }
+
+ static decimal CalculateTax(
+ decimal amount, string twoLetterRegionCode)
+ {
+ decimal rate = 0.0M;
+
+ // since we are matching string values a switch
+ // statement is easier than a switch expression
+
+ switch (twoLetterRegionCode)
+ {
+ case "CH": // Switzerland
+ rate = 0.08M;
+ break;
+ case "DK": // Denmark
+ case "NO": // Norway
+ rate = 0.25M;
+ break;
+ case "GB": // United Kingdom
+ case "FR": // France
+ rate = 0.2M;
+ break;
+ case "HU": // Hungary
+ rate = 0.27M;
+ break;
+ case "OR": // Oregon
+ case "AK": // Alaska
+ case "MT": // Montana
+ rate = 0.0M;
+ break;
+ case "ND": // North Dakota
+ case "WI": // Wisconsin
+ case "ME": // Maine
+ case "VA": // Virginia
+ rate = 0.05M;
+ break;
+ case "CA": // California
+ rate = 0.0825M;
+ break;
+ default: // most US states
+ rate = 0.06M;
+ break;
+ }
+
+ return amount * rate;
+ }
+
+ static int Factorial(int number)
+ {
+ if (number < 0)
+ {
+ throw new ArgumentException(
+ message: "The factorial function is defined for non-negative integers only.",
+ paramName: "number");
+ }
+ else if (number == 0)
+ {
+ return 1;
+ }
+ else
+ {
+ checked // for overflow
+ {
+ return number * Factorial(number - 1);
+ }
+ }
+ }
+
+ static int FibImperative(int term)
+ {
+ if (term == 1)
+ {
+ return 0;
+ }
+ else if (term == 2)
+ {
+ return 1;
+ }
+ else
+ {
+ return FibImperative(term - 1) + FibImperative(term - 2);
+ }
+ }
+
+}
diff --git a/vscode/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml b/vscode/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml
new file mode 100644
index 0000000..d2591f9
--- /dev/null
+++ b/vscode/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml
@@ -0,0 +1,32 @@
+@page
+@using Northwind.Web.Pages
+@using Packt.Shared
+@model CustomerOrdersModel
+@{
+ string title = "Customer and their orders";
+ ViewData["Title"] = $"Northwind B2B - {title}";
+}
+
+
@title
+
+ @if (Model.Customer is not null)
+ {
+
+
@Model.Customer.CompanyName
+
+
+
+
+ | Order Id | Order Date |
+
+
+ @foreach (Order o in Model.Customer.Orders)
+ {
+ | @o.OrderId | @o.OrderDate |
+ }
+
+
+
+ }
+
+
\ No newline at end of file
diff --git a/vscode/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml.cs b/vscode/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml.cs
new file mode 100644
index 0000000..a73fc74
--- /dev/null
+++ b/vscode/PracticalApps/Northwind.Web/Pages/CustomerOrders.cshtml.cs
@@ -0,0 +1,25 @@
+using Microsoft.AspNetCore.Mvc.RazorPages; // PageModel
+using Microsoft.EntityFrameworkCore; // Include extension method
+using Packt.Shared; // Customer
+
+namespace Northwind.Web.Pages;
+
+public class CustomerOrdersModel : PageModel
+{
+ public Customer? Customer;
+
+ private NorthwindContext db;
+
+ public CustomerOrdersModel(NorthwindContext db)
+ {
+ this.db = db;
+ }
+
+ public void OnGet()
+ {
+ string id = HttpContext.Request.Query["id"];
+
+ Customer = db.Customers.Include(c => c.Orders)
+ .SingleOrDefault(c => c.CustomerId == id);
+ }
+}
diff --git a/vscode/PracticalApps/Northwind.Web/Pages/Customers.cshtml b/vscode/PracticalApps/Northwind.Web/Pages/Customers.cshtml
new file mode 100644
index 0000000..f33a4f0
--- /dev/null
+++ b/vscode/PracticalApps/Northwind.Web/Pages/Customers.cshtml
@@ -0,0 +1,40 @@
+@page
+@using Northwind.Web.Pages
+@using Packt.Shared
+@model CustomersModel
+@{
+ string title = "Customers by Country";
+ ViewData["Title"] = $"Northwind B2B - {title}";
+}
+
+
@title
+
+
Exercise 14.2 – Practice building a data-driven web page
+
+
+ @if (Model.CustomersByCountry is not null)
+ {
+ @foreach (IGrouping
cbc in Model.CustomersByCountry)
+ {
+
+ }
+ }
+
+
\ No newline at end of file
diff --git a/vscode/PracticalApps/Northwind.Web/Pages/Customers.cshtml.cs b/vscode/PracticalApps/Northwind.Web/Pages/Customers.cshtml.cs
new file mode 100644
index 0000000..03a1f5e
--- /dev/null
+++ b/vscode/PracticalApps/Northwind.Web/Pages/Customers.cshtml.cs
@@ -0,0 +1,21 @@
+using Microsoft.AspNetCore.Mvc.RazorPages; // PageModel
+using Packt.Shared; // Customer
+
+namespace Northwind.Web.Pages;
+
+public class CustomersModel : PageModel
+{
+ public ILookup? CustomersByCountry;
+
+ private NorthwindContext db;
+
+ public CustomersModel(NorthwindContext db)
+ {
+ this.db = db;
+ }
+
+ public void OnGet()
+ {
+ CustomersByCountry = db.Customers.ToLookup(c => c.Country);
+ }
+}
diff --git a/vscode/PracticalApps/Northwind.Web/Pages/Functions.cshtml b/vscode/PracticalApps/Northwind.Web/Pages/Functions.cshtml
new file mode 100644
index 0000000..4c7e491
--- /dev/null
+++ b/vscode/PracticalApps/Northwind.Web/Pages/Functions.cshtml
@@ -0,0 +1,190 @@
+@page
+@using Northwind.Web.Pages
+@using Packt.Shared
+@model FunctionsModel
+@{
+ string title = "Functions";
+ ViewData["Title"] = $"Northwind B2B - {title}";
+
+ string collapsedTimesTable = Model.TimesTableNumberInput.HasValue ? string.Empty : "collapse";
+ string collapsedCalculateTax = Model.Amount.HasValue ? string.Empty : "collapse";
+ string collapsedFactorial = Model.FactorialNumber.HasValue ? string.Empty : "collapse";
+ string collapsedFibonacci = Model.FibonacciNumber.HasValue ? string.Empty : "collapse";
+}
+
+
@title
+
+
Exercise 14.3 – Practice building web pages for console apps
+
Provide a web user interface to output times tables, calculate tax, and generate factorials and the Fibonacci sequence.
+
+
+
+
+
+
+
+ @if (Model.TimesTableNumberInput.HasValue)
+ {
+
+
+
@Model.TimesTableNumberInput times table
+ @for (int i = 1; i <= 12; i++)
+ {
+
+ @i x @Model.TimesTableNumberInput = @(i * Model.TimesTableNumberInput)
+
+ }
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+ @if (Model.Amount.HasValue)
+ {
+
+
+
You must pay @Model.TaxToPay in tax.
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+ @if (Model.FactorialNumber.HasValue)
+ {
+
+
+
@(Model.FactorialNumber)!
+
+ @(Model.FactorialNumber)! = @(Model.FactorialResult is null ? "null" : Model.FactorialResult.Value.ToString("N0"))
+
+
+
+
+ }
+ @if (Model.FactorialException is not null)
+ {
+
+
+
Exception
+
+ @Model.FactorialException.Message
+
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+ @if (Model.FibonacciNumber.HasValue)
+ {
+
+
+
Fibonacci term @Model.FibonacciNumber
+
+ Term @Model.FibonacciNumber of the fibonacci sequence = @(Model.FibonacciResult is null ? "null" : Model.FibonacciResult.Value.ToString("N0"))
+
+
+
+
+ }
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vscode/PracticalApps/Northwind.Web/Pages/Functions.cshtml.cs b/vscode/PracticalApps/Northwind.Web/Pages/Functions.cshtml.cs
new file mode 100644
index 0000000..bc42abe
--- /dev/null
+++ b/vscode/PracticalApps/Northwind.Web/Pages/Functions.cshtml.cs
@@ -0,0 +1,141 @@
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace Northwind.Web.Pages;
+
+public class FunctionsModel : PageModel
+{
+ public int? TimesTableNumberInput { get; set; }
+
+ public decimal? Amount { get; set; }
+ public string? RegionCode { get; set; }
+ public decimal? TaxToPay { get; set; }
+
+ public int? FactorialNumber { get; set; }
+ public int? FactorialResult { get; set; }
+ public Exception? FactorialException { get; set; }
+
+ public int? FibonacciNumber { get; set; }
+ public int? FibonacciResult { get; set; }
+
+ public void OnGet()
+ {
+ // Times Table
+ if (int.TryParse(HttpContext.Request.Query["timesTableNumberInput"], out int i))
+ {
+ TimesTableNumberInput = i;
+ }
+
+ // Calculate Tax
+ if (decimal.TryParse(HttpContext.Request.Query["calculateTaxAmountInput"], out decimal amount))
+ {
+ Amount = amount;
+ RegionCode = HttpContext.Request.Query["calculateTaxRegionCodeInput"];
+ TaxToPay = CalculateTax(amount: amount, twoLetterRegionCode: RegionCode);
+ }
+
+ // Factorial
+ if (int.TryParse(HttpContext.Request.Query["factorialNumberInput"], out int fact))
+ {
+ FactorialNumber = fact;
+ try
+ {
+ FactorialResult = Factorial(fact);
+ }
+ catch (Exception ex)
+ {
+ FactorialException = ex;
+ }
+ }
+
+ // Fibonacci
+ if (int.TryParse(HttpContext.Request.Query["fibonacciNumberInput"], out int fib))
+ {
+ FibonacciNumber = fib;
+ FibonacciResult = FibImperative(term: fib);
+ }
+ }
+
+ static decimal CalculateTax(
+ decimal amount, string twoLetterRegionCode)
+ {
+ decimal rate = 0.0M;
+
+ // since we are matching string values a switch
+ // statement is easier than a switch expression
+
+ switch (twoLetterRegionCode)
+ {
+ case "CH": // Switzerland
+ rate = 0.08M;
+ break;
+ case "DK": // Denmark
+ case "NO": // Norway
+ rate = 0.25M;
+ break;
+ case "GB": // United Kingdom
+ case "FR": // France
+ rate = 0.2M;
+ break;
+ case "HU": // Hungary
+ rate = 0.27M;
+ break;
+ case "OR": // Oregon
+ case "AK": // Alaska
+ case "MT": // Montana
+ rate = 0.0M;
+ break;
+ case "ND": // North Dakota
+ case "WI": // Wisconsin
+ case "ME": // Maine
+ case "VA": // Virginia
+ rate = 0.05M;
+ break;
+ case "CA": // California
+ rate = 0.0825M;
+ break;
+ default: // most US states
+ rate = 0.06M;
+ break;
+ }
+
+ return amount * rate;
+ }
+
+ static int Factorial(int number)
+ {
+ if (number < 0)
+ {
+ throw new ArgumentException(
+ message: "The factorial function is defined for non-negative integers only.",
+ paramName: "number");
+ }
+ else if (number == 0)
+ {
+ return 1;
+ }
+ else
+ {
+ checked // for overflow
+ {
+ return number * Factorial(number - 1);
+ }
+ }
+ }
+
+ static int FibImperative(int term)
+ {
+ if (term == 1)
+ {
+ return 0;
+ }
+ else if (term == 2)
+ {
+ return 1;
+ }
+ else
+ {
+ return FibImperative(term - 1) + FibImperative(term - 2);
+ }
+ }
+
+}