Errata (48 items)
If you find any mistakes, then please raise an issue in this repository or email me at markjprice (at) gmail.com.
- Page 24 - Revealing the namespace for the Program class
- Page 29 - Writing code using VS Code
- Page 91 - Comparing double and decimal types
- Page 106 - Rider and its warnings about boxing
- Page 112 - Custom number formatting
- Page 147 - Understanding how foreach works internally
- Page 175 - Throwing overflow exceptions with the checked statement
- Page 179 - Test your knowledge of operators
- Page 208 - Using the Visual Studio Code integrated terminal during debugging
- Page 252 - Changing an enum base type for performance
- Page 267 - Controlling how parameters are passed
- Page 298 - Defining a primary constructor for a class
- Page 383 - Creating a .NET Standard class library
- Page 392 - Publishing a self-contained app
- Page 392 - Publishing a self-contained app
- Page 393 - Publishing a single-file app
- Page 400 - Publishing a native AOT project
- Page 435 - Searching in strings
- Page 437 - Understanding the syntax of a regular expression
- Page 444 - Improving regular expression performance with source generators
- Page 446 - Storing multiple objects in collections
- Page 461 - Read-only, immutable, and frozen collections
- Page 469 - Returning collections from members
- Page 483 - Managing directories, Managing files
- Page 532 - Setting up SQLite CLI tools for Windows
- Page 538 - Defining the Northwind database context class
- Page 563 - Getting the generated SQL
- Page 570 - Getting a single entity
- Page 640 - Customizing the model and defining an extension method
- Page 644 - Improving the class-to-table mapping
- Page 646 - Improving the class-to-table mapping
- Page 650 - Testing the class libraries, Page 693 - Build a data-driven web page, Page 694 - Build web pages for functions
- Page 660 - Creating an empty ASP.NET Core project, Page 701 - Creating an ASP.NET Core Web API project
- Page 673 - Enabling static and default files
- Page 680 - Enabling Blazor static SSR
- Page 683 - Adding code to a Blazor static SSR page
- Page 692 - Configuring Entity Framework Core as a service
- Page 711 - Running the Blazor Web App project template
- Page 737 - ASP.NET Core Minimal APIs projects
- Page 748 - Distributed caching
- Page 750 - Creating data repositories with caching for entities
- Page 754 - Configuring the customer repository
- Page 756 - Configuring the customer repository
- Page 757 - Configuring the customer repository
- Page 780 - Companion books to continue your learning journey
- Exercise 13.2 – practice exercises - Build web pages for functions
- Appendix - Page 3
- Appendix - Page 5
Thanks to John Tempest in an email for raising this issue on December 21, 2024.
In the Good Practice box, I wrote, "Code editors have a feature named code snippets. These allow you to insert pieces of code that you commonly use, by typing a shortcut and pressing Tab twice." But you only need to press Tab once.
Thanks to Andriko in the book's Discord channel for asking a question about this issue.
I wrote, "In the preceding steps, I showed you how to use the dotnet CLI to create solutions and projects. Finally, with the August 2024 or later releases of the C# Dev Kit, VS Code has an improved project creation experience that provides you access to the same options you can use when creating a new project through the dotnet
CLI. To enable this ability, you must change a setting, as shown in the following configuration:"
"csharp.experimental.dotnetNewIntegration": true
This feature is no longer in preview so you do not need to enable it. In the next edition, I will remove the sentence about enabling it and the setting.
Thanks to Anass Sabiri for raising this issue on February 19, 2025.
In the Good Practice box, the first link has moved to https://www-users.cse.umn.edu/~arnold/disasters/patriot.html.
Thanks to Anass Sabiri for raising this issue on February 20, 2025.
The link for best practices has moved to this link: https://docs.unity3d.com/Manual/performance-reference-types.html, and you need to scroll down to one of the last sections, titled Avoid converting value types to reference types.
Thanks to Donald Maisey for raising this issue on February 5, 2025.
In Step 1, I tell the reader to enter some code that calls the WriteLine
method without the Console.
prefix. I do not introduce simplifying the statement like that for another two pages. In the next edition, I will add the prefix, as shown in the following code:
decimal value = 0.325M;
Console.WriteLine("Currency: {0:C}, Percentage: {0:0.0%}", value);
Thanks to Justin Treher for raising this issue on January 3, 2025.
In Step 1, I wrote, "Type statements to create an array of string variables and then output the length of each one, as shown in the following code:"
string[] names = { "Adam", "Barry", "Charlie" };
foreach (string name in names)
{
WriteLine($"{name} has {name.Length} characters.");
}
I then wrote, "The compiler turns the foreach
statement in the preceding example into something like the following pseudocode:"
IEnumerator e = names.GetEnumerator();
while (e.MoveNext())
{
string name = (string)e.Current; // Current is read-only!
WriteLine($"{name} has {name.Length} characters.");
}
But the names
variable is an array, and although arrays implement the IEnumerable<T>
interface as described in this section, the compiler is smart enough to ignore that and instead write a for
loop that uses the Length
property of the array since that is more efficient than using the IEnumerable
interface.
In the next edition, I will use a List<string>
instead of an array for the names
variable, and add a note that arrays are treated as a special case by the compiler.
Thanks to Justin Treher for raising this issue on January 3, 2025.
In Step 3, I wrote, "let’s get the compiler to warn us about the overflow by wrapping the statements using a checked
statement block", but it is not the compiler that warns us, it is the runtime that detects the overflow and throws the exception. In the next edition I will replace "compiler" with "runtime".
Thanks to Donald Maisey for raising this issue on February 15, 2025.
For the name of the exercise project I wrote Ch03Ex03Operators
when I should have written Exercise_Operators
.
Thanks to kingace9371 for asking a question about this, and to rene for providing the answer in the book's Discord channel.
In Step 7, I wrote, "In the launch.json
file editor, click the Add Configuration... button, and then select .NET: Launch .NET Core Console App"
The name of this option is now called .NET: Launch Executable file (Console), as shown in the following figure:
Thanks to Donald Maisey for raising this issue on February 22, 2025 and Bart Hofland for encouraging me to look again at this issue on April 21, 2025.
At the end of this section, I wrote, "Let’s see some real-life examples of when you would need to change an enum
from deriving from int
to deriving from another integer type:
- You want to increase the size of the integer to store more than 16 options in a flag
enum
. The defaultint
only allows 16 options: 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, and 16384. Changing touint
would double the number of choices to 32 without using any more space in memory. Changing toulong
would give 64 options. Changing toushort
would allow the same 16 options in half the bytes."
But as Donald and Bart point out, only the first bit sets the number negative. The other 31 bits can be used for enum
values. I should have written:
- You want to increase the size of the integer to store more than 31 options in a flag
enum
. The defaultint
only allows 31 options because one bit is needed to indicate a negative number. Changing touint
would an extra 32th value without using any more space in memory. The following table summarizes the number of options available for each integer type when used as the base type for aenum
:
Base Type | Maximum Values |
---|---|
sbyte |
7 |
byte |
8 |
short |
15 |
ushort |
16 |
int |
31 |
uint |
32 |
long |
63 |
ulong |
64 |
So if a uint
would give one extra option, why does C# default to using an int
as the base type for enums?
C# enums default to int
as their underlying type primarily because it's the most common and efficient integer type in .NET, not because it's the optimal choice in terms of bit range. It's a trade-off based on performance, interoperability, and historical convention rather than capacity.
uint
gives one more positive value but signed int
is the default integer type in C# and .NET. It’s used in for-loops, array indexing, and almost every system API. That means an enum
based on int
is easier to work with by default, avoiding implicit cast warnings or needing explicit conversions.
int
is also CLS-compliant, uint
is not. The Common Language Specification (CLS) defines a set of rules for .NET language interoperability and it doesn’t include uint
. So if you define an enum
with uint
, it can’t be used as-is from some .NET languages like Visual Basic .NET. int
enums are just safer across the .NET ecosystem.
Thanks to Donald Maisey for raising this issue on February 26, 2025.
In Step 1, I wrote, "In Person.cs
, add statements to define a method with three parameters, one in
parameter,
one ref
parameter, and one out
parameter, as shown in the following method:"
public void PassingParameters(int w, in int x, ref int y, out int z)
{
I should have written, "In Person.cs
, add statements to define a method with four int
parameters: one parameter without any modifier, and three parameters decorated with the modifiers in
, ref
, and out
, as shown in the following method:"
I will also change the order of the bullet descriptions to match the order of the parameters in the method.
Thanks to P9avel for raising this issue on February 16, 2025.
In Step 6, I wrote, "In Headset.cs
, add a default parameterless constructor, as highlighted in the following code:"
namespace Packt.Shared;
public class Headset(string manufacturer, string productName)
{
public string Manufacturer { get; set; } = manufacturer;
public string ProductName { get; set; } = productName;
// Default parameterless constructor calls the primary constructor.
public Headset() : this("Microsoft", "HoloLens") { }
}
But the note box underneath the code says, "Note the use of this()
to call the constructor of the base class and pass two parameters to it when the default constructor of Headset
is called." The note box should say, "Note the use of this()
to call the primary constructor and pass two parameters to it when the default constructor of Headset
is called."
Thanks to P9avel for raising this issue on February 18, 2025.
In the Good Practice box, the final version number was written as 0
. It should be 9.0
.
Thanks to P9avel for raising this issue on February 18, 2025.
In Step 5, I wrote, "note the output folders for the five OSes." There were five folders in earlier editions but in the 9th edition I reduced this. In the next edition, I will remove the word "five".
Thanks to P9avel for raising this issue on February 18, 2025.
In Step 7, Figure 7.3, the screenshot shows net8.0
in the address bar. It should be net9.0
.
Thanks to Vlad Alexandru Meici for raising this issue on December 8, 2024.
In the two command lines, I used /p
to set a project property when I should have used -p
. The complete command is:
dotnet publish -r win10-x64 -c Release --no-self-contained -p:PublishSingleFile=true
Thanks to Nathan Wolfe for raising this issue on January 14, 2025.
I wrote, "A console app that functions correctly during development when the code is untrimmed and JIT-compiled could still fail once you publish it using native AOT because then the code is trimmed and JIT-compiled and, therefore, it is a different code with different behavior."
But I mistakenly repeated "JIT-compiled" when I meant "AOT-compiled".
I should have written, "A console app that functions correctly during development when the code is untrimmed and JIT-compiled could still fail once you publish it using native AOT because then the code is trimmed and AOT-compiled and, therefore, it is a different code with different behavior."
Thanks to Donald Maisey for raising this issue on March 21, 2025.
In the second code block, in the last statement, I used a variable named vowelsSearchValues
, as shown in the following code:
WriteLine($"sentence.IndexOfAny(vowelsSearchValues): {
sentence.IndexOfAny(namesSearchValues)}");
I should have used namesSearchValues
, as shown in the following code:
WriteLine($"sentence.IndexOfAny(namesSearchValues): {
sentence.IndexOfAny(namesSearchValues)}");
Thanks to rene in the book's Discord channel for raising this issue.
In Table 8.7, the entry for {,3}
is wrong. That is not a valid range and so it actually matches the exact string {,3}
! To match "Up to three", you should use {0,3}
or {1,3}
depending on whether you want to accept zero or one as the lowest value. I will fix this in the 10th edition.
Thanks to P9avel for raising this issue on February 21, 2025.
In Steps 2, 3, and 4, I wrote "partial
method", when I should have written "partial
property".
Thanks to P9avel for raising this issue on February 21, 2025.
In Table 8.9, I wrote Dictionary<T>
. This should be Dictionary<TKey, TValue>
. I will also change the order to put it last in the list of collection examples for that row, and add a sentence to the description to explain, "Dictionaries need two types to be specified: one for the key and one for the value."
Thanks to P9avel for raising this issue on February 22, 2025.
I wrote, "Although the ReadOnlyCollection<T>
has to have an Add
and a Remove
method because it implements ICollection<T>
, it throws a NotImplementedException
to prevent changes." But it actually throws a NotSupportedException
.
Thanks to P9avel for raising this issue on February 22, 2025.
In the last code block in this section, I wrote a statement of code that used a ,
instead of a .
:
return Array,Empty<Person>();
It should be:
return Array.Empty<Person>();
Thanks to Vlad Alexandru Meici for raising this issue on December 31, 2024.
After prompting the user to press any key to delete the directory or file, the code should have an extra statement to output a new line otherwise the next text written to the console will appear immediately at the end of the "Press any key..." text.
This has been fixed in the code solutions here: https://github.com/markjprice/cs13net9/commit/d75644ad74bf3ffbd9ff202e0bf6f2ad665ca5ea
Thanks to P9avel for raising this issue on February 28, 2025.
In Step 3, I wrote sqlite-tools-win32-x86-3460100.zip
when I should have written sqlite-tools-win-x64-3460100.zip
.
Thanks to P9avel for raising this issue on February 28, 2025.
In the first sentence of this section, I wrote "A class named Northwind
" when I should have written "A class named NorthwindDb
".
Thanks to Mike_H/
mike_h_16837
in the Discord channel for this book for raising this issue on March 16, 2025.
In Step 1, I wrote, "In the QueryingProducts
method, before using the foreach
statement to enumerate the query,
add a statement to output the generated SQL, as shown in the following code:"
// Calling ToQueryString does not execute against the database.
// LINQ to Entities just converts the LINQ query to an SQL statement.
Info($"ToQueryString: {products.ToQueryString()}");
In Step 2, I wrote, "Run the code, enter a minimum value for units in stock, like 95
, and view the result, as shown
in the following partial output:"
Enter a minimum for units in stock: 95
Connection: Data Source=C:\cs13net9\Chapter10\WorkingWithEFCore\bin\
Debug\net9.0\Northwind.db
Info > ToQueryString: .param set @__stock_0 95
SELECT "c"."CategoryId", "c"."CategoryName", "c"."Description",
"t"."ProductId", "t"."CategoryId", "t"."UnitPrice", "t"."Discontinued",
"t"."ProductName", "t"."UnitsInStock"
FROM "Categories" AS "c"
LEFT JOIN (
SELECT "p"."ProductId", "p"."CategoryId", "p"."UnitPrice",
"p"."Discontinued", "p"."ProductName", "p"."UnitsInStock"
FROM "Products" AS "p"
WHERE "p"."UnitsInStock" >= @__stock_0
) AS "t" ON "c"."CategoryId" = "t"."CategoryId"
ORDER BY "c"."CategoryId"
Beverages has 2 products with a minimum of 95 units in stock.
Sasquatch Ale has 111 units in stock.
Rhönbräu Klosterbier has 125 units in stock.
...
But I mixed up two methods. The output is from adding a statement to call the ToQueryString
method in the FilteredIncludes
method. The code solution adds the call to two methods:
FilteredIncludes
: https://github.com/markjprice/cs13net9/blob/main/code/Chapter10/WorkingWithEFCore/Program.Queries.cs#L90QueryingProducts
: https://github.com/markjprice/cs13net9/blob/main/code/Chapter10/WorkingWithEFCore/Program.Queries.cs#L129
In the next edition, I will tell the reader to add the call the ToQueryString
method in both the FilteredIncludes
method and the QueryingProducts
method, and show the results of running both.
Thanks to es-moises for raising this issue on January 22, 2025.
In Step 3, the output in two places shows part of the WHERE
clause as "p"."ProductId" > @__id_0
but both should be "p"."ProductId" = @__id_0
.
Thanks to Mike_H/
mike_h_16837
for raising this issue on March 22, 2025 in the Discord channel for this book.
In Step 3, the following statement should not be there because the dotnet-ef
reverse-engineering does not add it:
entity.Property(e => e.Discontinued)
.HasDefaultValue((short)0);
Thanks to Mike_H/
mike_h_16837
for raising this issue on March 22, 2025 in the Discord channel for this book.
In Step 4, I wrote, "Set Find and Replace to search files in the Current project." But the last project you were actively working on is the Northwind.DataContext.Sqlite
project, not the Northwind.EntityModels.Sqlite
project.
In the next edition, I will add a preceding step to tell the reader to make the Northwind.EntityModels.Sqlite
project the current project.
In Step 9, we can improve the text, as shown in this errata from Tools and Skills for .NET 8.
Page 650 - Testing the class libraries, Page 693 - Build a data-driven web page, Page 694 - Build web pages for functions
I end the Testing the class libraries section by writing, "Finally, in this chapter, let’s review some key concepts about web development, enabling us to be better prepared to dive into ASP.NET Core Razor Pages in the next chapter."
In the Build a data-driven web page exercise, I wrote, "Add a Razor Page to the Northwind.Web
website..."
In the Build web pages for functions exercise, I wrote, "Reimplement some of the console apps from earlier chapters as Razor Pages;"
In these instances, "Razor Pages" or "Razor Page" should be "Blazor" or "Blazor page component".
Page 660 - Creating an empty ASP.NET Core project, Page 701 - Creating an ASP.NET Core Web API project
Thanks to rene in the Discord channel for this book for raising this issue on February 6, 2025.
In Step 1, I describe the options when creating a new ASP.NET Core project. The option that used to be labelled Enable Docker is now labelled Enable container support. And the new option labelled Enlist in .NET Aspire orchestration should be cleared.
Thanks to Donald Maisey for raising this issue on April 18, 2025.
In Step 1, I wrote, "add statements after enabling HTTPS redirection to enable static files and default files" with the following code:
app.UseDefaultFiles(); // index.html, default.html, and so on.
app.MapStaticAssets(); // .NET 9 or later.
// app.UseStaticFiles(); // .NET 8 or earlier.
app.MapGet("/env", () =>
$"Environment is {app.Environment.EnvironmentName}");
But MapStaticAssets
method has a dependency on Razor components, so we also need to add them earlier than I currently do on page 682 in Step 12. In the next edition, I will move Step 12 to page 673 before Step 1. Here's the step:
- In
Program.cs
, after the statement that creates thebuilder
, add a statement to add ASP.NET Core Blazor components and their related services, and optionally define a#region
, as shown in the following code:
#region Configure the web server host and services.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents();
var app = builder.Build();
#endregion
Thanks to Donald Maisey for raising this issue on April 24, 2025.
In Step 4, I wrote, "In _Imports.razor
, add statements to import the namespaces for Blazor components routing,
and for your Northwind.Web
project and its components, as shown in the following markup:"
@using Microsoft.AspNetCore.Components.Routing
@using Northwind.Web
@using Northwind.Web.Components
I missed out a fourth import (needed to use components like <HeadOutlet />
), as shown in the following markup:
@using Microsoft.AspNetCore.Components.Web
Thanks to Taylor Fore for raising this issue on January 8, 2025.
In Step 1, I wrote, Index.cshtml
when I should have written Index.razor
.
Thanks to Mike_H/
mike_h_16837
for raising this issue on March 25, 2025 in the Discord channel for this book.
In Step 9, the if
statement, as shown in the following code:
@if (Model.Suppliers is not null)
Should be:
@if (Companies is not null)
Thanks to Mike_H/
mike_h_16837
for raising this issue on March 26, 2025 in the Discord channel for this book.
In Step 4, "click Weather" should be "click Counter".
Thanks to P9avel for raising this issue on March 10, 2025.
In the Good Practice box, I wrote, "A major benefit of Minimal APIs over a controller-based Web API is that each Minimal API endpoint only needs to instantiate the dependency injection (DI) services that it needs. With controllers, to execute any action method within that controller, all DI services used in any of the action methods must be instantiated for every call. This is a waste of time and resources!"
This is only true if the controller follows the common pattern of getting dependency services in the constructor of the class and storing them in private fields, as shown in the following code:
// Base address: api/customers
[Route("api/[controller]")]
[ApiController]
public class CustomersController : ControllerBase
{
private readonly ICustomerRepository _repo;
private readonly ILogger<CustomersController> _logger;
// Constructor injects repository registered in Program.cs.
public CustomersController(ICustomerRepository repo,
ILogger<CustomersController> logger)
{
_repo = repo;
_logger = logger;
}
// GET: api/customers
// GET: api/customers/?country=[country]
// this will always return a list of customers (but it might be empty)
[HttpGet]
[ProducesResponseType(200, Type = typeof(IEnumerable<Customer>))]
public async Task<IEnumerable<Customer>> GetCustomers(string? country)
{
if (string.IsNullOrWhiteSpace(country))
{
_logger.LogInformation("GetCustomers called for all countries.");
return await _repo.RetrieveAllAsync();
}
else
{
_logger.LogInformation("GetCustomers called with country={country}", country);
return (await _repo.RetrieveAllAsync())
.Where(customer => customer.Country == country);
}
}
...
}
A better practice is to use method injection, as shown in the following code:
// Base address: api/customers
[Route("api/[controller]")]
[ApiController]
public class CustomersController : ControllerBase
{
// GET: api/customers
// GET: api/customers/?country=[country]
// this will always return a list of customers (but it might be empty)
[HttpGet]
[ProducesResponseType(200, Type = typeof(IEnumerable<Customer>))]
public async Task<IEnumerable<Customer>> GetCustomers(string? country,
ICustomerRepository _repo,
ILogger<CustomersController> _logger)
{
if (string.IsNullOrWhiteSpace(country))
{
_logger.LogInformation("GetCustomers called for all countries.");
return await _repo.RetrieveAllAsync();
}
else
{
_logger.LogInformation("GetCustomers called with country={country}", country);
return (await _repo.RetrieveAllAsync())
.Where(customer => customer.Country == country);
}
}
...
}
Note: You can decorate the parameters with
[FromServices]
to explicitly indicate where those parameters will be set from, as shown in the following code:[FromServices] ICustomerRepository _repo
, but this is optional.
In the next edition, I will change the Good Practice box text to explain that the inefficiency can be avoided but that it is a common pattern because the project template uses it to define a logger and many developers follow that convention for their own dependency services. I will also show more detailed code for using both the constructor and methods to set dependency services in my Real-World Web Development with .NET 10 book which teaches controller-based ASP.NET Core Web API projects.
Thanks to P9avel for raising this issue on March 10, 2025.
After the four bullets, I wrote, "To implement in-memory caching, add it to the services collection in Program.cs
, as shown in the
following code:"
services.AddStackExchangeRedisCache(options =>
options.Configuration = "localhost:6379";
options.InstanceName = "SampleInstance";
});
Instead of "in-memory", I should have written "distributed". In the next edition, I will change the sentence to be, "To implement distributed caching, add a distributed caching implementation, like Redis, to the services collection in Program.cs
, as shown in the following code:"
Thanks to Jeroen for asking a question about this issue.
In Step 3, I wrote, "In Program.cs
, before the call to Build
, in the section for configuring services, register the hybrid cache service..."
Unfortunately, even after the release of .NET 9 on November 12, 2024, the Microsoft.Extensions.Caching.Hybrid
package was still in preview, and the AddHybridCache
method call caused a compiler warning. Until the package was released as GA on March 11, 2025, you had to surround the call with a temporary warning suppression, as shown in the following code:
#pragma warning disable EXTEXP0018 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
builder.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromSeconds(60),
LocalCacheExpiration = TimeSpan.FromSeconds(30)
};
});
#pragma warning restore EXTEXP0018 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
If you reference package version '9.3.0' or later then you do no longer need these #pragma
statements in Program.cs
.
Thanks to P9avel for raising this issue on March 10, 2025.
In the Good Practice box, I wrote, "Our repository uses a database context that is registered as a scoped dependency. You can only use scoped dependencies inside other scoped dependencies, so we cannot register the repository as a singleton."
I should have written, "By default, a database context is registered as a scoped dependency. Our repository uses a database context that is registered as a transient dependency. This is only necessary if you want to use the database context in a Blazor Server project otherwise we could leave it as a scoped dependency. You can only use scoped or transient dependencies inside other scoped or transient dependencies, so we cannot register the repository as a singleton. In this case, we can register it as a scoped dependency so during a single HTTP request, it can be reused by multiple scoped or transient dependency services."
Thanks to Andrea for raising this issue on February 12, 2025.
In Step 7, the statement that returns a 204 No content
response mistakenly included the new
keyword, as shown in the following code:
return new TypedResults.NoContent(); // 204 No content.
That statement should be:
return TypedResults.NoContent(); // 204 No content.
It was already correct in the code solution repo: https://github.com/markjprice/cs13net9/blob/main/code/ModernWeb/Northwind.WebApi/Program.Customers.cs#L79
Thanks to P9avel for raising this issue on March 10, 2025.
In the paragraph after Step 10, I wrote, "When an HTTP request is received by the service, it will create an instance of the Controller class, call the appropriate action method, return the response in the format preferred by the client, and release the resources used by the controller, including the repository and its data context."
I should have written, "When an HTTP request is received by the service, it will use the mapped routes to call the appropriate endpoint lambda statement handler, get the registered dependency service(s) and use them, and then at the end of the scope, release those resources, including the repository and its data context."
Thanks to Jort Rodenburg for raising this issue on January 31, 2025.
The order of the four books in Figure 16.1 does not match the order of the numbered descriptions. Numbers 3. and 4. should be swapped.
This exercise should have been in Chapter 14 Building Interactive Web Components Using Blazor, because it is much easier to implement using interactive Blazor features rather than the limited Blazor static server-side rendering (SSR).
Updated functions page to use server-side interactivity: https://github.com/markjprice/cs13net9/blob/main/code/ModernWeb/Northwind.Blazor/Northwind.Blazor/Components/Pages/Functions.razor
Updated navmenu to include link to functions page: https://github.com/markjprice/cs13net9/blob/main/code/ModernWeb/Northwind.Blazor/Northwind.Blazor/Components/Layout/NavMenu.razor
Thanks to Donald Maisey for raising this issue on February 5, 2025.
I re-arranged the practices and exercises at the end of Chapter 2 but forgot to also update the order in the appendix:
- Exercise 2.1 – Test your knowledge should be Exercise 2.3 – Test your knowledge.
- Exercise 2.2 – Test your knowledge of number types is a second part to Exercise 2.3.
- Exercise 2.3 – Practice number sizes and ranges should be Exercise 2.2.
Thanks to Donald Maisey for raising this issue on February 15, 2025.
I re-arranged the practices and exercises at the end of Chapter 2 but forgot to also update the order in the appendix:
- Exercise 3.1 – Test your knowledge should be Exercise 3.3 – Test your knowledge.
- Exercise 3.2 – Explore loops and overflow should be Exercise 3.2 - Practice exercises with sub-sections Loops and overflow, Practice loops and operators, and Practice exception handling.
- Exercise 3.3 – Test your knowledge of operators should be a subsection of Exercise 3.3 – Test your knowledge.
It looks like I forgot to update all chapters in the appendix but it should be obvious enough for readers to understand. I will fix them all in the next edition.