Skip to content

Commit

Permalink
Add solution code for Ch15
Browse files Browse the repository at this point in the history
  • Loading branch information
markjprice committed Jul 28, 2023
1 parent 2aec9d2 commit af60d21
Show file tree
Hide file tree
Showing 31 changed files with 1,046 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using System.Xml.Serialization; // To use [XmlIgnore].

namespace Northwind.EntityModels;

Expand Down Expand Up @@ -51,9 +52,11 @@ public partial class Customer
public string? Fax { get; set; }

[InverseProperty("Customer")]
[XmlIgnore]
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();

[ForeignKey("CustomerId")]
[InverseProperty("Customers")]
[XmlIgnore]
public virtual ICollection<CustomerDemographic> CustomerTypes { get; set; } = new List<CustomerDemographic>();
}
2 changes: 2 additions & 0 deletions code/PracticalApps/Northwind.EntityModels.Sqlite/Customer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using System.Xml.Serialization; // To use [XmlIgnore].

namespace Northwind.EntityModels;

Expand Down Expand Up @@ -59,5 +60,6 @@ public partial class Customer
public string? Fax { get; set; }

[InverseProperty("Customer")]
[XmlIgnore]
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<PublishAot>true</PublishAot>
</PropertyGroup>

</Project>
11 changes: 11 additions & 0 deletions code/PracticalApps/Northwind.MinimalApi/Northwind.MinimalApi.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@Northwind.MinimalApi_HostAddress = http://localhost:5279

GET {{Northwind.MinimalApi_HostAddress}}/todos/
Accept: application/json

###

GET {{Northwind.MinimalApi_HostAddress}}/todos/1
Accept: application/json

###
28 changes: 28 additions & 0 deletions code/PracticalApps/Northwind.MinimalApi/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Northwind.MinimalApi;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
? Results.Ok(todo)
: Results.NotFound());

app.Run();

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "todos",
"applicationUrl": "http://localhost:5152",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
60 changes: 60 additions & 0 deletions code/PracticalApps/Northwind.MinimalApi/Todo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
namespace Northwind.MinimalApi
{
public class Todo
{
public int Id { get; set; }

public string? Title { get; set; }

public DateOnly? DueBy { get; set; }

public bool IsComplete { get; set; }
}

internal static class TodoGenerator
{
private static readonly (string[] Prefixes, string[] Suffixes)[] _parts = new[]
{
(new[] { "Walk the", "Feed the" }, new[] { "dog", "cat", "goat" }),
(new[] { "Do the", "Put away the" }, new[] { "groceries", "dishes", "laundry" }),
(new[] { "Clean the" }, new[] { "bathroom", "pool", "blinds", "car" })
};

internal static IEnumerable<Todo> GenerateTodos(int count = 5)
{
var titleCount = _parts.Sum(row => row.Prefixes.Length * row.Suffixes.Length);
var titleMap = new (int Row, int Prefix, int Suffix)[titleCount];
var mapCount = 0;
for (var i = 0; i < _parts.Length; i++)
{
var prefixes = _parts[i].Prefixes;
var suffixes = _parts[i].Suffixes;
for (var j = 0; j < prefixes.Length; j++)
{
for (var k = 0; k < suffixes.Length; k++)
{
titleMap[mapCount++] = (i, j, k);
}
}
}

Random.Shared.Shuffle(titleMap);

for (var id = 1; id <= count; id++)
{
var (rowIndex, prefixIndex, suffixIndex) = titleMap[id];
var (prefixes, suffixes) = _parts[rowIndex];
yield return new Todo
{
Id = id,
Title = string.Join(' ', prefixes[prefixIndex], suffixes[suffixIndex]),
DueBy = Random.Shared.Next(-200, 365) switch
{
< 0 => null,
var days => DateOnly.FromDateTime(DateTime.Now.AddDays(days))
}
};
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions code/PracticalApps/Northwind.MinimalApi/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
135 changes: 133 additions & 2 deletions code/PracticalApps/Northwind.Mvc/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly NorthwindContext _db;
private readonly IHttpClientFactory _clientFactory;

public HomeController(ILogger<HomeController> logger,
NorthwindContext db)
public HomeController(
ILogger<HomeController> logger,
NorthwindContext db,
IHttpClientFactory clientFactory)
{
_logger = logger;
_db = db;
_clientFactory = clientFactory;
}

[ResponseCache(Duration = 10 /* seconds */,
Expand All @@ -36,6 +40,27 @@ public async Task<IActionResult> Index()
Products: await _db.Products.ToListAsync()
);

try
{
HttpClient client = _clientFactory.CreateClient(
name: "Northwind.MinimalApi");

HttpRequestMessage request = new(
method: HttpMethod.Get, requestUri: "todos");

HttpResponseMessage response = await client.SendAsync(request);

ViewData["todos"] = await response.Content
.ReadFromJsonAsync<ToDo[]>();
}
catch (Exception ex)
{
_logger.LogWarning(
$"The Minimal.WebApi service is not responding. Exception: {ex.Message}");

ViewData["todos"] = Enumerable.Empty<ToDo>().ToArray();
}

return View(model); // Pass the model to the view.
}

Expand Down Expand Up @@ -114,5 +139,111 @@ public IActionResult ProductsThatCostMoreThan(decimal? price)

return View(model);
}

public async Task<IActionResult> Customers(string country)
{
string uri;

if (string.IsNullOrEmpty(country))
{
ViewData["Title"] = "All Customers Worldwide";
uri = "api/customers";
}
else
{
ViewData["Title"] = $"Customers in {country}";
uri = $"api/customers/?country={country}";
}

HttpClient client = _clientFactory.CreateClient(
name: "Northwind.WebApi");

HttpRequestMessage request = new(
method: HttpMethod.Get, requestUri: uri);

HttpResponseMessage response = await client.SendAsync(request);

IEnumerable<Customer>? model = await response.Content
.ReadFromJsonAsync<IEnumerable<Customer>>();

return View(model);
}

// 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)
{
TempData["success-message"] = "Customer successfully added.";
}
else
{
TempData["error-message"] = "Customer was NOT added.";
}

// Show the full customers list to see if it was added.
return RedirectToAction("Customers");
}

// GET /Home/DeleteCustomer/{customerId}
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)
{
TempData["success-message"] = "Customer successfully deleted.";
}
else
{
TempData["error-message"] = $"Customer {customerId} was NOT deleted.";
}

// Show the full customers list to see if it was deleted.
return RedirectToAction("Customers");
}
}
}
3 changes: 3 additions & 0 deletions code/PracticalApps/Northwind.Mvc/Models/Todo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Northwind.Mvc.Models;

public record ToDo(int Id, string? Title, DateOnly? DueBy, bool IsComplete);
4 changes: 4 additions & 0 deletions code/PracticalApps/Northwind.Mvc/Northwind.Mvc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@
<Folder Include="wwwroot\images\" />
</ItemGroup>

<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Net.SocketsHttpHandler.Http3Support" Value="true" />
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions code/PracticalApps/Northwind.Mvc/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Microsoft.EntityFrameworkCore; // To use UseSqlServer method.
using Northwind.Mvc.Data; // To use ApplicationDbContext.
using Northwind.EntityModels; // To use AddNorthwindContext method.
using System.Net.Http.Headers; // To use MediaTypeWithQualityHeaderValue.
using System.Net; // To use HttpVersion.
#endregion

#region Configure the host web server including services
Expand Down Expand Up @@ -50,6 +52,27 @@
options.AddPolicy("views", p => p.SetVaryByQuery("alertstyle"));
});

builder.Services.AddHttpClient(name: "Northwind.WebApi",
configureClient: options =>
{
options.DefaultRequestVersion = HttpVersion.Version30;
options.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
options.BaseAddress = new Uri("https://localhost:5151/");
options.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue(
mediaType: "application/json", quality: 1.0));
});

builder.Services.AddHttpClient(name: "Northwind.MinimalApi",
configureClient: options =>
{
options.BaseAddress = new Uri("http://localhost:5152/");
options.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue(
"application/json", 1.0));
});

var app = builder.Build();

#endregion
Expand Down
Loading

0 comments on commit af60d21

Please sign in to comment.