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
+
+
+ + + + + + @foreach (Order o in Model.Customer.Orders) + { + + } + +
Order IdOrder Date
@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.
+
+
+
+

+ +

+
+
+
+
+ + +
Enter an integer between 1 and 100.
+
+ +
+ @if (Model.TimesTableNumberInput.HasValue) + { +
+
+
@Model.TimesTableNumberInput times table
+ @for (int i = 1; i <= 12; i++) + { +
+ @i x @Model.TimesTableNumberInput = @(i * Model.TimesTableNumberInput) +
+ } +
+ +
+ } +
+
+
+ +
+

+ +

+
+
+
+
+ + +
Enter a monetary value.
+
+
+ + +
Select a European or US state.
+
+ +
+ @if (Model.Amount.HasValue) + { +
+
+
You must pay @Model.TaxToPay in tax.
+
+ +
+ } +
+
+
+ +
+

+ +

+
+
+
+
+
+ + +
Enter an integer between 1 and 12.
+
+ +
+ @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 +
+
+ +
+ } +
+
+
+
+ +
+

+ +

+
+
+
+
+
+ + +
Enter an integer between 1 and 40.
+
+ +
+ @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
+
+
+ + + + + + @foreach (Order o in Model.Customer.Orders) + { + + } + +
Order IdOrder Date
@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.
+
+
+
+

+ +

+
+
+
+
+ + +
Enter an integer between 1 and 100.
+
+ +
+ @if (Model.TimesTableNumberInput.HasValue) + { +
+
+
@Model.TimesTableNumberInput times table
+ @for (int i = 1; i <= 12; i++) + { +
+ @i x @Model.TimesTableNumberInput = @(i * Model.TimesTableNumberInput) +
+ } +
+ +
+ } +
+
+
+ +
+

+ +

+
+
+
+
+ + +
Enter a monetary value.
+
+
+ + +
Select a European or US state.
+
+ +
+ @if (Model.Amount.HasValue) + { +
+
+
You must pay @Model.TaxToPay in tax.
+
+ +
+ } +
+
+
+ +
+

+ +

+
+
+
+
+
+ + +
Enter an integer between 1 and 12.
+
+ +
+ @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 +
+
+ +
+ } +
+
+
+
+ +
+

+ +

+
+
+
+
+
+ + +
Enter an integer between 1 and 40.
+
+ +
+ @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); + } + } + +}