mirror of
https://github.com/markjprice/cs11dotnet7.git
synced 2026-01-27 18:24:15 +01:00
Add items for pages 650 and 700
This commit is contained in:
parent
89a4fb5fa4
commit
f4f1d216d5
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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<IActionResult> 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)
|
||||
{
|
||||
<p class="alert alert-danger">Error! @ViewData["error-message"]</p>
|
||||
}
|
||||
@if (ViewData["success-message"] is not null)
|
||||
{
|
||||
<p class="alert alert-success">Congratulations! @ViewData["success-message"]</p>
|
||||
}
|
||||
|
||||
<a asp-controller="Home" asp-action="AddCustomer"
|
||||
class="btn btn-outline-primary">Add Customer</a>
|
||||
```
|
||||
|
||||
**In the table of customers**
|
||||
```html
|
||||
<td>
|
||||
<a asp-controller="Home"
|
||||
asp-action="DeleteCustomer"
|
||||
asp-route-customerid="@c.CustomerId"
|
||||
class="btn btn-outline-danger">Delete</a>
|
||||
</td>
|
||||
```
|
||||
|
||||
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<IActionResult> 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<Customer>();
|
||||
|
||||
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<IActionResult> DeleteCustomer(string customerId)
|
||||
{
|
||||
HttpClient client = clientFactory.CreateClient(
|
||||
name: "Northwind.WebApi");
|
||||
|
||||
Customer? customer = await client.GetFromJsonAsync<Customer>(
|
||||
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<IActionResult> 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
|
||||
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
|
||||
<!--
|
||||
Show an editable form with a blank customer. Postback to the action
|
||||
method AddCustomer to perform the actual insert.
|
||||
-->
|
||||
<form asp-action="AddCustomer" method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="customerId">Customer ID</label>
|
||||
@Html.EditorFor(model => model.CustomerId,
|
||||
new { htmlAttributes = new { @class = "form-control" } })
|
||||
<div class="form-text">
|
||||
Customer ID must be five (5) upper case characters.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="companyName">Company Name</label>
|
||||
@Html.EditorFor(model => model.CompanyName,
|
||||
new { htmlAttributes = new { @class = "form-control" } })
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="contactName">Contact Name</label>
|
||||
@Html.EditorFor(model => model.CompanyName,
|
||||
new { htmlAttributes = new { @class = "form-control" } })
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="address">Address</label>
|
||||
@Html.EditorFor(model => model.CompanyName,
|
||||
new { htmlAttributes = new { @class = "form-control" } })
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="city">City</label>
|
||||
@Html.EditorFor(model => model.CompanyName,
|
||||
new { htmlAttributes = new { @class = "form-control" } })
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="region">Region</label>
|
||||
@Html.EditorFor(model => model.CompanyName,
|
||||
new { htmlAttributes = new { @class = "form-control" } })
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="country">Country</label>
|
||||
@Html.EditorFor(model => model.CompanyName,
|
||||
new { htmlAttributes = new { @class = "form-control" } })
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="postalCode">Postal Code</label>
|
||||
@Html.EditorFor(model => model.CompanyName,
|
||||
new { htmlAttributes = new { @class = "form-control" } })
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="phone">Phone</label>
|
||||
@Html.EditorFor(model => model.CompanyName,
|
||||
new { htmlAttributes = new { @class = "form-control" } })
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="submit" value="Add Customer"
|
||||
class="btn btn-outline-primary" />
|
||||
<a asp-controller="Home" asp-action="Customers"
|
||||
class="btn btn-outline-secondary">
|
||||
Cancel and return to Customers
|
||||
</a>
|
||||
@Html.ValidationSummary()
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
**DeleteCustomer.cshtml Razor view**
|
||||
```html
|
||||
@using Packt.Shared
|
||||
@model Customer
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
|
||||
<!--
|
||||
Show a readonly form of an existing customer. Postback to the action
|
||||
method DeleteCustomer to perform the actual delete.
|
||||
-->
|
||||
<form asp-action="DeleteCustomer" method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="customerId">Customer ID</label>
|
||||
<input class="form-control" id="customerId" value="@Model.CustomerId" readonly />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="companyName">Company Name</label>
|
||||
<input class="form-control" id="companyName" value="@Model.CompanyName" readonly />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="contactName">Contact Name</label>
|
||||
<input class="form-control" id="contactName" value="@Model.ContactName" readonly />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="address">Address</label>
|
||||
<input class="form-control" id="address" value="@Model.Address" readonly />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="city">City</label>
|
||||
<input class="form-control" id="city" value="@Model.City" readonly />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="region">Region</label>
|
||||
<input class="form-control" id="region" value="@Model.Region" readonly />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="country">Country</label>
|
||||
<input class="form-control" id="country" value="@Model.Country" readonly />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="postalCode">Postal Code</label>
|
||||
<input class="form-control" id="postalCode" value="@Model.PostalCode" readonly />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="phone">Phone</label>
|
||||
<input class="form-control" id="phone" value="@Model.Phone" readonly />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@Html.HiddenFor(c => c.CustomerId)
|
||||
<input type="submit" value="Delete Customer"
|
||||
class="btn btn-outline-danger" />
|
||||
<a href="customers" class="btn btn-outline-secondary">
|
||||
Cancel and return to Customers
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
|
|
|||
Loading…
Reference in a new issue