From f4f1d216d5750d9e2c33fd660c5de46f9582956e Mon Sep 17 00:00:00 2001 From: Mark J Price Date: Thu, 8 Jun 2023 10:46:24 +0100 Subject: [PATCH] Add items for pages 650 and 700 --- docs/errata/README.md | 2 +- docs/errata/improvements.md | 268 +++++++++++++++++++++++++++++++++++- 2 files changed, 268 insertions(+), 2 deletions(-) diff --git a/docs/errata/README.md b/docs/errata/README.md index 2f891b9..57c0565 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** (44 items)](errata.md): Typos, tool user interface changes, or mistakes in code that would cause a compilation error that prevents a successful build. -[**Improvements** (35 items)](improvements.md): Changes to text or code that would improve the content. These are optional. +[**Improvements** (37 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 71a2e52..555cf27 100644 --- a/docs/errata/improvements.md +++ b/docs/errata/improvements.md @@ -1,4 +1,4 @@ -**Improvements** (35 items) +**Improvements** (37 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. @@ -36,8 +36,10 @@ If you have suggestions for improvements, then please [raise an issue in this re - [Page 573 - Adding code to a Razor Page](#page-573---adding-code-to-a-razor-page) - [Page 586 - Creating a Razor class library, Page 587 - Implementing a partial view to show a single employee](#page-586---creating-a-razor-class-library-page-587---implementing-a-partial-view-to-show-a-single-employee) - [Page 601 - Setting up an ASP.NET Core MVC website](#page-601---setting-up-an-aspnet-core-mvc-website) +- [Page 650 - Varying cached data by query string](#page-650---varying-cached-data-by-query-string) - [Page 654 - Making controller action methods asynchronous](#page-654---making-controller-action-methods-asynchronous) - [Page 655 - Exercise 14.2 – Practice implementing MVC by implementing a category detail page](#page-655---exercise-142--practice-implementing-mvc-by-implementing-a-category-detail-page) +- [Page 700 - Exercise 15.2 – Practice creating and deleting customers with HttpClient](#page-700---exercise-152--practice-creating-and-deleting-customers-with-httpclient) # Page 25 - Adding a second project using Visual Studio 2022 @@ -952,6 +954,19 @@ In the next edition, I will improve this text and add a warning: "**Views**: *Razor View* files, that is, `.cshtml` files, that render data in view models into HTML web pages. *Razor Views* are different from *Razor Pages* but they share the same file extension `.cshtml`. When creating a *Razor Page*, it must have the `@page` directive at the top of its file. When creating a *Razor View*, do NOT use the `@page` directive! If you do, the controller will not pass the model and it will be `null`, throwing a `NullReferenceException` when you try to access any of its members. Also note that Blazor uses the `.razor` file extension, but do not confuse them with *Razor View* or *Razor Page* files! To add even more confusion, Blazor can also use the `@page` directive to allow a Blazor component to act like a page!" +# Page 650 - Varying cached data by query string + +At the end of this section, you will have enabled output caching for the default MVC route that ignores query string variants. This could be confusing if you forget this behavior. + +In the next edition, at the end of this section I will add a step to disable the output caching, as shown in the following code: +```cs +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); +// Comment out the following: +// .CacheOutput("views"); +``` + # Page 654 - Making controller action methods asynchronous In an earlier task, you imported the `Microsoft.EntityFrameworkCore` namespace so that you could use the `Include` extension method. In Step 1, I tell you to use the `ToListAsync` method to implement the `Index` action method asynchronously. If you had not previously imported the `Microsoft.EntityFrameworkCore` namespace then you would have to import it now to use the `ToListAsync` method. @@ -1029,3 +1044,254 @@ If you want to keep the original link format, then you would need to decorate th [Route("category/{id}")] public async Task CategoryDetail(int? id) ``` + +# Page 700 - Exercise 15.2 – Practice creating and deleting customers with HttpClient + +In this exercise, you are tasked to extend the `Northwind.Mvc` website project to have pages where a visitor can fill in a form to create a new customer, or search for a customer and then delete them. The MVC controller should make calls to the Northwind web service to create and delete customers. Without addtiional help and with no solution initially, it can be tricky for a new reader to complete this exercise. + +I have now provided a possible solution and updated the GitHub repository with it. In the next edition, I will also provide some hints, similar to the following. + +The project already has a page to show customers, either all of them or only those in a specified country. To avoid work, we will use this existing functionality. We just need to add a button in the `Customers.cshtml` view to **Add Customer**, a **Delete** button in a new column for each customer in the table, and areas to output success and errors messages, as shown in the following code: + +**At the top of the Customers page** +```html +@if (ViewData["error-message"] is not null) +{ +

Error! @ViewData["error-message"]

+} +@if (ViewData["success-message"] is not null) +{ +

Congratulations! @ViewData["success-message"]

+} + +Add Customer +``` + +**In the table of customers** +```html + + Delete + +``` + +A common design pattern for implementing add and delete functionality in an ASP.NET Core MVC website is to define pairs of action methods, one that responds to a `GET` request and one that responds to a `POST` request. + +- `GET /Home/AddCustomer`: This request means show me a page with a blank form to enter information about a new customer. +- `POST /Home/AddCustomer`: This request means perform the actual customer insert using the form data provided in the body. + +- `GET /Home/DeleteCustomer/{ID}`: This request means show me a page with a form loaded with an existing customer record to confirm this is the customer the will be deleted. +- `POST /Home/DeleteCustomer`: This request means perform the actual customer delete using the form data provided in the body. + +Here is example code that would implement this functionality: +```cs +// GET /Home/AddCustomer +public IActionResult AddCustomer() +{ + ViewData["Title"] = "Add Customer"; + return View(); +} + +// POST /Home/AddCustomer +// A Customer object in the request body. +[HttpPost] +public async Task AddCustomer(Customer customer) +{ + HttpClient client = clientFactory.CreateClient( + name: "Northwind.WebApi"); + + HttpResponseMessage response = await client.PostAsJsonAsync( + requestUri: "api/customers", value: customer); + + // Optionally, get the created customer back as JSON + // so the user can see the assigned ID, for example. + Customer? model = await response.Content + .ReadFromJsonAsync(); + + if (response.IsSuccessStatusCode) + { + ViewData["success-message"] = "Customer successfully added."; + } + else + { + ViewData["error-message"] = "Customer was NOT added."; + } + + // Show the full customers list to see if it was added. + return RedirectToAction("Customers"); +} + +// GET /Home/DeleteCustomer +public async Task DeleteCustomer(string customerId) +{ + HttpClient client = clientFactory.CreateClient( + name: "Northwind.WebApi"); + + Customer? customer = await client.GetFromJsonAsync( + requestUri: $"api/customers/{customerId}"); + + ViewData["Title"] = "Delete Customer"; + + return View(customer); +} + +// POST /Home/DeleteCustomer +// A CustomerId in the request body e.g. ALFKI. +[HttpPost] +[Route("home/deletecustomer")] +// Action method name must have a different name from the GET method +// due to C# not allowing duplicate method signatures. +public async Task DeleteCustomerPost(string customerId) +{ + HttpClient client = clientFactory.CreateClient( + name: "Northwind.WebApi"); + + HttpResponseMessage response = await client.DeleteAsync( + requestUri: $"api/customers/{customerId}"); + + if (response.IsSuccessStatusCode) + { + ViewData["success-message"] = "Customer successfully deleted."; + } + else + { + ViewData["error-message"] = $"Customer {customerId} was NOT deleted."; + } + + // Show the full customers list to see if it was deleted. + return RedirectToAction("Customers"); +} +``` + +**AddCustomer.cshtml Razor view** +```html +@using Packt.Shared +@model Customer + +

@ViewData["Title"]

+ + +
+
+ + @Html.EditorFor(model => model.CustomerId, + new { htmlAttributes = new { @class = "form-control" } }) +
+ Customer ID must be five (5) upper case characters. +
+
+
+ + @Html.EditorFor(model => model.CompanyName, + new { htmlAttributes = new { @class = "form-control" } }) +
+
+ + @Html.EditorFor(model => model.CompanyName, + new { htmlAttributes = new { @class = "form-control" } }) +
+
+ + @Html.EditorFor(model => model.CompanyName, + new { htmlAttributes = new { @class = "form-control" } }) +
+
+ + @Html.EditorFor(model => model.CompanyName, + new { htmlAttributes = new { @class = "form-control" } }) +
+
+ + @Html.EditorFor(model => model.CompanyName, + new { htmlAttributes = new { @class = "form-control" } }) +
+
+ + @Html.EditorFor(model => model.CompanyName, + new { htmlAttributes = new { @class = "form-control" } }) +
+
+ + @Html.EditorFor(model => model.CompanyName, + new { htmlAttributes = new { @class = "form-control" } }) +
+
+ + @Html.EditorFor(model => model.CompanyName, + new { htmlAttributes = new { @class = "form-control" } }) +
+
+ + + Cancel and return to Customers + + @Html.ValidationSummary() +
+
+``` + +**DeleteCustomer.cshtml Razor view** +```html +@using Packt.Shared +@model Customer +

@ViewData["Title"]

+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ @Html.HiddenFor(c => c.CustomerId) + + + Cancel and return to Customers + +
+
+```