From d071a88f3e17311e3c543c5e321db58223327a31 Mon Sep 17 00:00:00 2001 From: David Pine Date: Wed, 22 Sep 2021 15:04:37 -0500 Subject: [PATCH 1/6] Added signalr bits, minor clean up --- src/BlazingPizza.Client/OrderState.cs | 4 +- src/BlazingPizza.Client/OrdersClient.cs | 12 +++--- .../Pages/OrderDetails.razor.cs | 13 +++--- .../Hubs/IOrderStatusHub.cs | 4 -- .../Hubs/OrderStatusHub.cs | 21 ++++++---- src/BlazingPizza.Server/OrdersController.cs | 42 ++++++++----------- src/BlazingPizza.Server/Startup.cs | 4 -- 7 files changed, 44 insertions(+), 56 deletions(-) diff --git a/src/BlazingPizza.Client/OrderState.cs b/src/BlazingPizza.Client/OrderState.cs index 5265ae39..b42258e7 100644 --- a/src/BlazingPizza.Client/OrderState.cs +++ b/src/BlazingPizza.Client/OrderState.cs @@ -8,11 +8,11 @@ public class OrderState public Pizza ConfiguringPizza { get; private set; } - public Order Order { get; private set; } = new Order(); + public Order Order { get; private set; } = new(); public void ShowConfigurePizzaDialog(PizzaSpecial special) { - ConfiguringPizza = new Pizza() + ConfiguringPizza = new Pizza { Special = special, SpecialId = special.Id, diff --git a/src/BlazingPizza.Client/OrdersClient.cs b/src/BlazingPizza.Client/OrdersClient.cs index 01c63572..93ecfd8f 100644 --- a/src/BlazingPizza.Client/OrdersClient.cs +++ b/src/BlazingPizza.Client/OrdersClient.cs @@ -9,24 +9,24 @@ namespace BlazingPizza.Client { public class OrdersClient { - private readonly HttpClient httpClient; + private readonly HttpClient _httpClient; public OrdersClient(HttpClient httpClient) { - this.httpClient = httpClient; + _httpClient = httpClient; } public async Task> GetOrders() => - await httpClient.GetFromJsonAsync>("orders"); + await _httpClient.GetFromJsonAsync>("orders"); public async Task GetOrder(int orderId) => - await httpClient.GetFromJsonAsync($"orders/{orderId}"); + await _httpClient.GetFromJsonAsync($"orders/{orderId}"); public async Task PlaceOrder(Order order) { - var response = await httpClient.PostAsJsonAsync("orders", order); + var response = await _httpClient.PostAsJsonAsync("orders", order); response.EnsureSuccessStatusCode(); var orderId = await response.Content.ReadFromJsonAsync(); return orderId; @@ -34,7 +34,7 @@ public async Task PlaceOrder(Order order) public async Task SubscribeToNotifications(NotificationSubscription subscription) { - var response = await httpClient.PutAsJsonAsync("notifications/subscribe", subscription); + var response = await _httpClient.PutAsJsonAsync("notifications/subscribe", subscription); response.EnsureSuccessStatusCode(); } } diff --git a/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs b/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs index 3143846a..275f11d3 100644 --- a/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs +++ b/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs @@ -32,7 +32,7 @@ protected override async Task OnInitializedAsync() .Build(); _hubConnection.On( - OrderStatusHubConsts.EventNames.OrderStatusChanged, OnOrderStatusChangedAsync); + "OrderStatusChanged", OnOrderStatusChangedAsync); await _hubConnection.StartAsync(); } @@ -41,7 +41,7 @@ private async Task GetAccessTokenValueAsync() { var result = await AccessTokenProvider.RequestAccessToken(); return result.TryGetToken(out var accessToken) - ? accessToken.Value + ? accessToken.Value : null; } @@ -55,14 +55,12 @@ protected override async Task OnParametersSetAsync() if (_orderWithStatus.IsDelivered) { - await _hubConnection.InvokeAsync( - OrderStatusHubConsts.MethodNames.StopTrackingOrder, _orderWithStatus.Order); + await _hubConnection.InvokeAsync("StopTrackingOrder", _orderWithStatus.Order); await _hubConnection.StopAsync(); } else { - await _hubConnection.InvokeAsync( - OrderStatusHubConsts.MethodNames.StartTrackingOrder, _orderWithStatus.Order); + await _hubConnection.InvokeAsync("StartTrackingOrder", _orderWithStatus.Order); } } catch (AccessTokenNotAvailableException ex) @@ -90,8 +88,7 @@ public async ValueTask DisposeAsync() { if (_orderWithStatus is not null) { - await _hubConnection.InvokeAsync( - OrderStatusHubConsts.MethodNames.StopTrackingOrder, _orderWithStatus.Order); + await _hubConnection.InvokeAsync("StopTrackingOrder", _orderWithStatus.Order); } await _hubConnection.DisposeAsync(); diff --git a/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs b/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs index 3e118f08..72b428db 100644 --- a/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs +++ b/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs @@ -4,10 +4,6 @@ namespace BlazingPizza.Server.Hubs { public interface IOrderStatusHub { - /// - /// This event name should match , - /// which is shared with clients for discoverability. - /// Task OrderStatusChanged(OrderWithStatus order); } } diff --git a/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs b/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs index 3117be36..da5ec7fd 100644 --- a/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs +++ b/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs @@ -1,5 +1,4 @@ -using BlazingPizza.Server.Extensions; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; @@ -11,21 +10,27 @@ public class OrderStatusHub : Hub /// /// Adds the current connection to the order's unique group identifier, where /// order status changes will be notified in real-time. - /// This method name should match , - /// which is shared with clients for discoverability. /// public Task StartTrackingOrder(Order order) => Groups.AddToGroupAsync( - Context.ConnectionId, order.ToOrderTrackingGroupId()); + Context.ConnectionId, ToOrderTrackingGroupId(order)); /// /// Removes the current connection from the order's unique group identifier, /// ending real-time change updates for this order. - /// This method name should match , - /// which is shared with clients for discoverability. /// public Task StopTrackingOrder(Order order) => Groups.RemoveFromGroupAsync( - Context.ConnectionId, order.ToOrderTrackingGroupId()); + Context.ConnectionId, ToOrderTrackingGroupId(order)); + + /// + /// Call to notify connected clients about changes to an order. + /// + public Task OrderUpdated(OrderWithStatus orderWithStatus) => + Clients.Group(ToOrderTrackingGroupId(orderWithStatus.Order)) + .OrderStatusChanged(orderWithStatus); + + private static string ToOrderTrackingGroupId(Order order) => + $"{order.OrderId}:{order.UserId}"; } } diff --git a/src/BlazingPizza.Server/OrdersController.cs b/src/BlazingPizza.Server/OrdersController.cs index 9ae26541..a63884d0 100644 --- a/src/BlazingPizza.Server/OrdersController.cs +++ b/src/BlazingPizza.Server/OrdersController.cs @@ -4,9 +4,10 @@ using System.Security.Claims; using System.Text.Json; using System.Threading.Tasks; -using BlazingPizza.Server.Services; +using BlazingPizza.Server.Hubs; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using WebPush; @@ -18,11 +19,13 @@ namespace BlazingPizza.Server public class OrdersController : Controller { private readonly PizzaStoreContext _db; - private readonly IBackgroundOrderQueue _orderQueue; + private readonly IHubContext _orderStatusHubContext; - public OrdersController( - PizzaStoreContext db, IBackgroundOrderQueue orderQueue) => - (_db, _orderQueue) = (db, orderQueue); + public OrdersController(PizzaStoreContext db, IHubContext orderStatusHubContext) + { + _db = db; + _orderStatusHubContext = orderStatusHubContext; + } [HttpGet] public async Task>> GetOrders() @@ -35,7 +38,7 @@ public async Task>> GetOrders() .OrderByDescending(o => o.CreatedTime) .ToListAsync(); - return orders.Select(OrderWithStatus.FromOrder).ToList(); + return orders.Select(o => OrderWithStatus.FromOrder(o)).ToList(); } [HttpGet("{orderId}")] @@ -81,7 +84,7 @@ public async Task> PlaceOrder(Order order) var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); if (subscription != null) { - await QueueNotificationsAsync(order, subscription); + _ = TrackAndSendNotificationsAsync(order, subscription); } return order.OrderId; @@ -92,26 +95,19 @@ private string GetUserId() return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); } - private async Task QueueNotificationsAsync( - Order order, NotificationSubscription subscription) + private static async Task TrackAndSendNotificationsAsync(Order order, NotificationSubscription subscription) { // In a realistic case, some other backend process would track // order delivery progress and send us notifications when it // changes. Since we don't have any such process here, fake it. - await _orderQueue.QueueBackgroundOrderStatusAsync(async canellationToken => - { - await Task.Delay(OrderWithStatus.PreparationDuration, canellationToken); - return await SendNotificationAsync(order, subscription, "Your order has been dispatched!"); - }); - - await _orderQueue.QueueBackgroundOrderStatusAsync(async canellationToken => - { - await Task.Delay(OrderWithStatus.DeliveryDuration, canellationToken); - return await SendNotificationAsync(order, subscription, "Your order is now delivered. Enjoy!"); - }); + await Task.Delay(OrderWithStatus.PreparationDuration); + await SendNotificationAsync(order, subscription, "Your order has been dispatched!"); + + await Task.Delay(OrderWithStatus.DeliveryDuration); + await SendNotificationAsync(order, subscription, "Your order is now delivered. Enjoy!"); } - private static async Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message) + private static async Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message) { // For a real application, generate your own var publicKey = "BLC8GOevpcpjQiLkO7JmVClQjycvTCYWm6Cq_a7wJZlstGTVZvwGFFHMYfXt6Njyvgx_GlXJeo5cSiZ1y4JOx1o"; @@ -131,10 +127,8 @@ private static async Task SendNotificationAsync(Order order, Notification } catch (Exception ex) { - Console.Error.WriteLine($"Error sending push notification: {ex.Message}"); + Console.Error.WriteLine("Error sending push notification: " + ex.Message); } - - return order; } } } diff --git a/src/BlazingPizza.Server/Startup.cs b/src/BlazingPizza.Server/Startup.cs index 88da7f72..38120101 100644 --- a/src/BlazingPizza.Server/Startup.cs +++ b/src/BlazingPizza.Server/Startup.cs @@ -1,5 +1,4 @@ using BlazingPizza.Server.Hubs; -using BlazingPizza.Server.Services; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -38,9 +37,6 @@ public void ConfigureServices(IServiceCollection services) services.AddSignalR(options => options.EnableDetailedErrors = true) .AddMessagePackProtocol(); - - services.AddHostedService(); - services.AddSingleton(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) From 9d6fb35fa1e798161a4f48d4e568382c9da4d8d8 Mon Sep 17 00:00:00 2001 From: David Pine Date: Thu, 23 Sep 2021 10:37:01 -0500 Subject: [PATCH 2/6] Added full support of SignalR server & client bits, minor clean up along the way. --- .../Pages/OrderDetails.razor.cs | 13 +++--- .../Hubs/IOrderStatusHub.cs | 3 ++ .../Hubs/OrderStatusHub.cs | 9 +--- src/BlazingPizza.Server/OrdersController.cs | 42 +++++++++++-------- .../Services/OrderStatusService.cs | 18 ++++---- src/BlazingPizza.Server/Startup.cs | 4 ++ src/BlazingPizza.Shared/OrderWithStatus.cs | 15 +++++-- 7 files changed, 61 insertions(+), 43 deletions(-) diff --git a/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs b/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs index 275f11d3..3143846a 100644 --- a/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs +++ b/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs @@ -32,7 +32,7 @@ protected override async Task OnInitializedAsync() .Build(); _hubConnection.On( - "OrderStatusChanged", OnOrderStatusChangedAsync); + OrderStatusHubConsts.EventNames.OrderStatusChanged, OnOrderStatusChangedAsync); await _hubConnection.StartAsync(); } @@ -41,7 +41,7 @@ private async Task GetAccessTokenValueAsync() { var result = await AccessTokenProvider.RequestAccessToken(); return result.TryGetToken(out var accessToken) - ? accessToken.Value + ? accessToken.Value : null; } @@ -55,12 +55,14 @@ protected override async Task OnParametersSetAsync() if (_orderWithStatus.IsDelivered) { - await _hubConnection.InvokeAsync("StopTrackingOrder", _orderWithStatus.Order); + await _hubConnection.InvokeAsync( + OrderStatusHubConsts.MethodNames.StopTrackingOrder, _orderWithStatus.Order); await _hubConnection.StopAsync(); } else { - await _hubConnection.InvokeAsync("StartTrackingOrder", _orderWithStatus.Order); + await _hubConnection.InvokeAsync( + OrderStatusHubConsts.MethodNames.StartTrackingOrder, _orderWithStatus.Order); } } catch (AccessTokenNotAvailableException ex) @@ -88,7 +90,8 @@ public async ValueTask DisposeAsync() { if (_orderWithStatus is not null) { - await _hubConnection.InvokeAsync("StopTrackingOrder", _orderWithStatus.Order); + await _hubConnection.InvokeAsync( + OrderStatusHubConsts.MethodNames.StopTrackingOrder, _orderWithStatus.Order); } await _hubConnection.DisposeAsync(); diff --git a/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs b/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs index 72b428db..ebd3f6d6 100644 --- a/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs +++ b/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs @@ -4,6 +4,9 @@ namespace BlazingPizza.Server.Hubs { public interface IOrderStatusHub { + /// + /// This event name should match this: . + /// Task OrderStatusChanged(OrderWithStatus order); } } diff --git a/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs b/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs index da5ec7fd..74143339 100644 --- a/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs +++ b/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs @@ -10,6 +10,7 @@ public class OrderStatusHub : Hub /// /// Adds the current connection to the order's unique group identifier, where /// order status changes will be notified in real-time. + /// This method name should match this: . /// public Task StartTrackingOrder(Order order) => Groups.AddToGroupAsync( @@ -18,18 +19,12 @@ public Task StartTrackingOrder(Order order) => /// /// Removes the current connection from the order's unique group identifier, /// ending real-time change updates for this order. + /// This method name should match this: . /// public Task StopTrackingOrder(Order order) => Groups.RemoveFromGroupAsync( Context.ConnectionId, ToOrderTrackingGroupId(order)); - /// - /// Call to notify connected clients about changes to an order. - /// - public Task OrderUpdated(OrderWithStatus orderWithStatus) => - Clients.Group(ToOrderTrackingGroupId(orderWithStatus.Order)) - .OrderStatusChanged(orderWithStatus); - private static string ToOrderTrackingGroupId(Order order) => $"{order.OrderId}:{order.UserId}"; } diff --git a/src/BlazingPizza.Server/OrdersController.cs b/src/BlazingPizza.Server/OrdersController.cs index a63884d0..9ae26541 100644 --- a/src/BlazingPizza.Server/OrdersController.cs +++ b/src/BlazingPizza.Server/OrdersController.cs @@ -4,10 +4,9 @@ using System.Security.Claims; using System.Text.Json; using System.Threading.Tasks; -using BlazingPizza.Server.Hubs; +using BlazingPizza.Server.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using WebPush; @@ -19,13 +18,11 @@ namespace BlazingPizza.Server public class OrdersController : Controller { private readonly PizzaStoreContext _db; - private readonly IHubContext _orderStatusHubContext; + private readonly IBackgroundOrderQueue _orderQueue; - public OrdersController(PizzaStoreContext db, IHubContext orderStatusHubContext) - { - _db = db; - _orderStatusHubContext = orderStatusHubContext; - } + public OrdersController( + PizzaStoreContext db, IBackgroundOrderQueue orderQueue) => + (_db, _orderQueue) = (db, orderQueue); [HttpGet] public async Task>> GetOrders() @@ -38,7 +35,7 @@ public async Task>> GetOrders() .OrderByDescending(o => o.CreatedTime) .ToListAsync(); - return orders.Select(o => OrderWithStatus.FromOrder(o)).ToList(); + return orders.Select(OrderWithStatus.FromOrder).ToList(); } [HttpGet("{orderId}")] @@ -84,7 +81,7 @@ public async Task> PlaceOrder(Order order) var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); if (subscription != null) { - _ = TrackAndSendNotificationsAsync(order, subscription); + await QueueNotificationsAsync(order, subscription); } return order.OrderId; @@ -95,19 +92,26 @@ private string GetUserId() return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); } - private static async Task TrackAndSendNotificationsAsync(Order order, NotificationSubscription subscription) + private async Task QueueNotificationsAsync( + Order order, NotificationSubscription subscription) { // In a realistic case, some other backend process would track // order delivery progress and send us notifications when it // changes. Since we don't have any such process here, fake it. - await Task.Delay(OrderWithStatus.PreparationDuration); - await SendNotificationAsync(order, subscription, "Your order has been dispatched!"); - - await Task.Delay(OrderWithStatus.DeliveryDuration); - await SendNotificationAsync(order, subscription, "Your order is now delivered. Enjoy!"); + await _orderQueue.QueueBackgroundOrderStatusAsync(async canellationToken => + { + await Task.Delay(OrderWithStatus.PreparationDuration, canellationToken); + return await SendNotificationAsync(order, subscription, "Your order has been dispatched!"); + }); + + await _orderQueue.QueueBackgroundOrderStatusAsync(async canellationToken => + { + await Task.Delay(OrderWithStatus.DeliveryDuration, canellationToken); + return await SendNotificationAsync(order, subscription, "Your order is now delivered. Enjoy!"); + }); } - private static async Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message) + private static async Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message) { // For a real application, generate your own var publicKey = "BLC8GOevpcpjQiLkO7JmVClQjycvTCYWm6Cq_a7wJZlstGTVZvwGFFHMYfXt6Njyvgx_GlXJeo5cSiZ1y4JOx1o"; @@ -127,8 +131,10 @@ private static async Task SendNotificationAsync(Order order, NotificationSubscri } catch (Exception ex) { - Console.Error.WriteLine("Error sending push notification: " + ex.Message); + Console.Error.WriteLine($"Error sending push notification: {ex.Message}"); } + + return order; } } } diff --git a/src/BlazingPizza.Server/Services/OrderStatusService.cs b/src/BlazingPizza.Server/Services/OrderStatusService.cs index 65256384..da48225e 100644 --- a/src/BlazingPizza.Server/Services/OrderStatusService.cs +++ b/src/BlazingPizza.Server/Services/OrderStatusService.cs @@ -1,5 +1,4 @@ -using BlazingPizza.Server.Extensions; -using BlazingPizza.Server.Hubs; +using BlazingPizza.Server.Hubs; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -15,27 +14,27 @@ namespace BlazingPizza.Server.Services { public sealed class OrderStatusService : BackgroundService { - private readonly IBackgroundOrderQueue _orderQueue; + private readonly IBackgroundOrderQueue _taskQueue; private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; public OrderStatusService( - IBackgroundOrderQueue orderQueue, + IBackgroundOrderQueue taskQueue, IServiceProvider serviceProvider, ILogger logger) => - (_orderQueue, _serviceProvider, _logger) = (orderQueue, serviceProvider, logger); + (_taskQueue, _serviceProvider, _logger) = (taskQueue, serviceProvider, logger); [SuppressMessage( "Style", "IDE0063:Use simple 'using' statement", - Justification = "We need explicit disposal of the IServiceScope to avoid errant conditions.")] + Justification = "We need explicit scoping of the provider")] protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { - var workItem = await _orderQueue.DequeueAsync(stoppingToken); + var workItem = await _taskQueue.DequeueAsync(stoppingToken); var order = await workItem(stoppingToken); using (var scope = _serviceProvider.CreateScope()) @@ -44,10 +43,9 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) scope.ServiceProvider .GetRequiredService>(); - // This is not production code, this is intended to act as a - // fake backend order processing system. DO NOT DO THIS. - var trackingOrderId = order.ToOrderTrackingGroupId(); + var trackingOrderId = $"{order.OrderId}:{order.UserId}"; var orderWithStatus = await GetOrderAsync(scope.ServiceProvider, order.OrderId); + while (!orderWithStatus.IsDelivered) { await hubContext.Clients.Group(trackingOrderId).OrderStatusChanged(orderWithStatus); diff --git a/src/BlazingPizza.Server/Startup.cs b/src/BlazingPizza.Server/Startup.cs index 38120101..88da7f72 100644 --- a/src/BlazingPizza.Server/Startup.cs +++ b/src/BlazingPizza.Server/Startup.cs @@ -1,4 +1,5 @@ using BlazingPizza.Server.Hubs; +using BlazingPizza.Server.Services; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -37,6 +38,9 @@ public void ConfigureServices(IServiceCollection services) services.AddSignalR(options => options.EnableDetailedErrors = true) .AddMessagePackProtocol(); + + services.AddHostedService(); + services.AddSingleton(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) diff --git a/src/BlazingPizza.Shared/OrderWithStatus.cs b/src/BlazingPizza.Shared/OrderWithStatus.cs index 021de78c..4c995758 100644 --- a/src/BlazingPizza.Shared/OrderWithStatus.cs +++ b/src/BlazingPizza.Shared/OrderWithStatus.cs @@ -70,10 +70,19 @@ private static LatLong ComputeStartPosition(Order order) var distance = 0.01 + rng.NextDouble() * 0.02; var angle = rng.NextDouble() * Math.PI * 2; var offset = (distance * Math.Cos(angle), distance * Math.Sin(angle)); - return new LatLong(order.DeliveryLocation.Latitude + offset.Item1, order.DeliveryLocation.Longitude + offset.Item2); + + return new LatLong( + order.DeliveryLocation.Latitude + offset.Item1, + order.DeliveryLocation.Longitude + offset.Item2); } - static Marker ToMapMarker(string description, LatLong coords, bool showPopup = false) - => new Marker { Description = description, X = coords.Longitude, Y = coords.Latitude, ShowPopup = showPopup }; + static Marker ToMapMarker(string description, LatLong coords, bool showPopup = false) => + new() + { + Description = description, + X = coords.Longitude, + Y = coords.Latitude, + ShowPopup = showPopup + }; } } From 8ee14e5775fd3953d1106bd74e2346fee2dc0980 Mon Sep 17 00:00:00 2001 From: David Pine Date: Thu, 23 Sep 2021 11:49:14 -0500 Subject: [PATCH 3/6] Clean up --- src/BlazingPizza.Client/OrderState.cs | 4 ++-- src/BlazingPizza.Client/OrdersClient.cs | 12 ++++++------ .../Hubs/IOrderStatusHub.cs | 3 ++- src/BlazingPizza.Server/Hubs/OrderStatusHub.cs | 16 ++++++++-------- .../Services/OrderStatusService.cs | 18 ++++++++++-------- src/BlazingPizza.Shared/OrderWithStatus.cs | 15 +++------------ 6 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/BlazingPizza.Client/OrderState.cs b/src/BlazingPizza.Client/OrderState.cs index b42258e7..5265ae39 100644 --- a/src/BlazingPizza.Client/OrderState.cs +++ b/src/BlazingPizza.Client/OrderState.cs @@ -8,11 +8,11 @@ public class OrderState public Pizza ConfiguringPizza { get; private set; } - public Order Order { get; private set; } = new(); + public Order Order { get; private set; } = new Order(); public void ShowConfigurePizzaDialog(PizzaSpecial special) { - ConfiguringPizza = new Pizza + ConfiguringPizza = new Pizza() { Special = special, SpecialId = special.Id, diff --git a/src/BlazingPizza.Client/OrdersClient.cs b/src/BlazingPizza.Client/OrdersClient.cs index 93ecfd8f..01c63572 100644 --- a/src/BlazingPizza.Client/OrdersClient.cs +++ b/src/BlazingPizza.Client/OrdersClient.cs @@ -9,24 +9,24 @@ namespace BlazingPizza.Client { public class OrdersClient { - private readonly HttpClient _httpClient; + private readonly HttpClient httpClient; public OrdersClient(HttpClient httpClient) { - _httpClient = httpClient; + this.httpClient = httpClient; } public async Task> GetOrders() => - await _httpClient.GetFromJsonAsync>("orders"); + await httpClient.GetFromJsonAsync>("orders"); public async Task GetOrder(int orderId) => - await _httpClient.GetFromJsonAsync($"orders/{orderId}"); + await httpClient.GetFromJsonAsync($"orders/{orderId}"); public async Task PlaceOrder(Order order) { - var response = await _httpClient.PostAsJsonAsync("orders", order); + var response = await httpClient.PostAsJsonAsync("orders", order); response.EnsureSuccessStatusCode(); var orderId = await response.Content.ReadFromJsonAsync(); return orderId; @@ -34,7 +34,7 @@ public async Task PlaceOrder(Order order) public async Task SubscribeToNotifications(NotificationSubscription subscription) { - var response = await _httpClient.PutAsJsonAsync("notifications/subscribe", subscription); + var response = await httpClient.PutAsJsonAsync("notifications/subscribe", subscription); response.EnsureSuccessStatusCode(); } } diff --git a/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs b/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs index ebd3f6d6..3e118f08 100644 --- a/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs +++ b/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs @@ -5,7 +5,8 @@ namespace BlazingPizza.Server.Hubs public interface IOrderStatusHub { /// - /// This event name should match this: . + /// This event name should match , + /// which is shared with clients for discoverability. /// Task OrderStatusChanged(OrderWithStatus order); } diff --git a/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs b/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs index 74143339..3117be36 100644 --- a/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs +++ b/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using BlazingPizza.Server.Extensions; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; @@ -10,22 +11,21 @@ public class OrderStatusHub : Hub /// /// Adds the current connection to the order's unique group identifier, where /// order status changes will be notified in real-time. - /// This method name should match this: . + /// This method name should match , + /// which is shared with clients for discoverability. /// public Task StartTrackingOrder(Order order) => Groups.AddToGroupAsync( - Context.ConnectionId, ToOrderTrackingGroupId(order)); + Context.ConnectionId, order.ToOrderTrackingGroupId()); /// /// Removes the current connection from the order's unique group identifier, /// ending real-time change updates for this order. - /// This method name should match this: . + /// This method name should match , + /// which is shared with clients for discoverability. /// public Task StopTrackingOrder(Order order) => Groups.RemoveFromGroupAsync( - Context.ConnectionId, ToOrderTrackingGroupId(order)); - - private static string ToOrderTrackingGroupId(Order order) => - $"{order.OrderId}:{order.UserId}"; + Context.ConnectionId, order.ToOrderTrackingGroupId()); } } diff --git a/src/BlazingPizza.Server/Services/OrderStatusService.cs b/src/BlazingPizza.Server/Services/OrderStatusService.cs index da48225e..65256384 100644 --- a/src/BlazingPizza.Server/Services/OrderStatusService.cs +++ b/src/BlazingPizza.Server/Services/OrderStatusService.cs @@ -1,4 +1,5 @@ -using BlazingPizza.Server.Hubs; +using BlazingPizza.Server.Extensions; +using BlazingPizza.Server.Hubs; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -14,27 +15,27 @@ namespace BlazingPizza.Server.Services { public sealed class OrderStatusService : BackgroundService { - private readonly IBackgroundOrderQueue _taskQueue; + private readonly IBackgroundOrderQueue _orderQueue; private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; public OrderStatusService( - IBackgroundOrderQueue taskQueue, + IBackgroundOrderQueue orderQueue, IServiceProvider serviceProvider, ILogger logger) => - (_taskQueue, _serviceProvider, _logger) = (taskQueue, serviceProvider, logger); + (_orderQueue, _serviceProvider, _logger) = (orderQueue, serviceProvider, logger); [SuppressMessage( "Style", "IDE0063:Use simple 'using' statement", - Justification = "We need explicit scoping of the provider")] + Justification = "We need explicit disposal of the IServiceScope to avoid errant conditions.")] protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { - var workItem = await _taskQueue.DequeueAsync(stoppingToken); + var workItem = await _orderQueue.DequeueAsync(stoppingToken); var order = await workItem(stoppingToken); using (var scope = _serviceProvider.CreateScope()) @@ -43,9 +44,10 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) scope.ServiceProvider .GetRequiredService>(); - var trackingOrderId = $"{order.OrderId}:{order.UserId}"; + // This is not production code, this is intended to act as a + // fake backend order processing system. DO NOT DO THIS. + var trackingOrderId = order.ToOrderTrackingGroupId(); var orderWithStatus = await GetOrderAsync(scope.ServiceProvider, order.OrderId); - while (!orderWithStatus.IsDelivered) { await hubContext.Clients.Group(trackingOrderId).OrderStatusChanged(orderWithStatus); diff --git a/src/BlazingPizza.Shared/OrderWithStatus.cs b/src/BlazingPizza.Shared/OrderWithStatus.cs index 4c995758..021de78c 100644 --- a/src/BlazingPizza.Shared/OrderWithStatus.cs +++ b/src/BlazingPizza.Shared/OrderWithStatus.cs @@ -70,19 +70,10 @@ private static LatLong ComputeStartPosition(Order order) var distance = 0.01 + rng.NextDouble() * 0.02; var angle = rng.NextDouble() * Math.PI * 2; var offset = (distance * Math.Cos(angle), distance * Math.Sin(angle)); - - return new LatLong( - order.DeliveryLocation.Latitude + offset.Item1, - order.DeliveryLocation.Longitude + offset.Item2); + return new LatLong(order.DeliveryLocation.Latitude + offset.Item1, order.DeliveryLocation.Longitude + offset.Item2); } - static Marker ToMapMarker(string description, LatLong coords, bool showPopup = false) => - new() - { - Description = description, - X = coords.Longitude, - Y = coords.Latitude, - ShowPopup = showPopup - }; + static Marker ToMapMarker(string description, LatLong coords, bool showPopup = false) + => new Marker { Description = description, X = coords.Longitude, Y = coords.Latitude, ShowPopup = showPopup }; } } From b771ae351f916c2c9835f9da997adbdf3ec40c99 Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 28 Sep 2021 14:37:14 -0500 Subject: [PATCH 4/6] Fix names of files, and better indicate that the bgs is 'fake'. --- ...kgroundTaskQueue.cs => DefaultBackgroundOrderQueue.cs} | 0 .../{OrderStatusService.cs => FakeOrderStatusService.cs} | 8 ++++---- .../{IBackgroundTaskQueue.cs => IBackgroundOrderQueue.cs} | 0 src/BlazingPizza.Server/Startup.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename src/BlazingPizza.Server/Services/{DefaultBackgroundTaskQueue.cs => DefaultBackgroundOrderQueue.cs} (100%) rename src/BlazingPizza.Server/Services/{OrderStatusService.cs => FakeOrderStatusService.cs} (91%) rename src/BlazingPizza.Server/Services/{IBackgroundTaskQueue.cs => IBackgroundOrderQueue.cs} (100%) diff --git a/src/BlazingPizza.Server/Services/DefaultBackgroundTaskQueue.cs b/src/BlazingPizza.Server/Services/DefaultBackgroundOrderQueue.cs similarity index 100% rename from src/BlazingPizza.Server/Services/DefaultBackgroundTaskQueue.cs rename to src/BlazingPizza.Server/Services/DefaultBackgroundOrderQueue.cs diff --git a/src/BlazingPizza.Server/Services/OrderStatusService.cs b/src/BlazingPizza.Server/Services/FakeOrderStatusService.cs similarity index 91% rename from src/BlazingPizza.Server/Services/OrderStatusService.cs rename to src/BlazingPizza.Server/Services/FakeOrderStatusService.cs index 65256384..d2001493 100644 --- a/src/BlazingPizza.Server/Services/OrderStatusService.cs +++ b/src/BlazingPizza.Server/Services/FakeOrderStatusService.cs @@ -13,16 +13,16 @@ namespace BlazingPizza.Server.Services { - public sealed class OrderStatusService : BackgroundService + public sealed class FakeOrderStatusService : BackgroundService { private readonly IBackgroundOrderQueue _orderQueue; private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; + private readonly ILogger _logger; - public OrderStatusService( + public FakeOrderStatusService( IBackgroundOrderQueue orderQueue, IServiceProvider serviceProvider, - ILogger logger) => + ILogger logger) => (_orderQueue, _serviceProvider, _logger) = (orderQueue, serviceProvider, logger); [SuppressMessage( diff --git a/src/BlazingPizza.Server/Services/IBackgroundTaskQueue.cs b/src/BlazingPizza.Server/Services/IBackgroundOrderQueue.cs similarity index 100% rename from src/BlazingPizza.Server/Services/IBackgroundTaskQueue.cs rename to src/BlazingPizza.Server/Services/IBackgroundOrderQueue.cs diff --git a/src/BlazingPizza.Server/Startup.cs b/src/BlazingPizza.Server/Startup.cs index 88da7f72..90859a3c 100644 --- a/src/BlazingPizza.Server/Startup.cs +++ b/src/BlazingPizza.Server/Startup.cs @@ -39,7 +39,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSignalR(options => options.EnableDetailedErrors = true) .AddMessagePackProtocol(); - services.AddHostedService(); + services.AddHostedService(); services.AddSingleton(); } From 1b076200432674fc3b33be5f175775eabd23ebdb Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 15 Nov 2021 11:14:39 -0600 Subject: [PATCH 5/6] Upgrade to .NET 6, enable implicit usings, prefer file-scoped namespaces, add GlobalUsings.cs, add solution items, and other misc clean up. --- .editorconfig | 192 +++++++++++++ Directory.Build.props | 9 +- .../BlazingComponents.csproj | 3 +- src/BlazingComponents/TemplatedList.razor | 23 +- .../BlazingPizza.Client.csproj | 7 +- src/BlazingPizza.Client/GlobalUsings.cs | 7 + .../JSRuntimeExtensions.cs | 20 +- src/BlazingPizza.Client/OrderState.cs | 99 +++---- src/BlazingPizza.Client/OrdersClient.cs | 68 ++--- .../Pages/Authentication.razor | 4 +- src/BlazingPizza.Client/Pages/Checkout.razor | 8 +- src/BlazingPizza.Client/Pages/Index.razor | 11 +- .../Pages/OrderDetails.razor.cs | 149 +++++----- .../PizzaAuthenticationState.cs | 13 +- src/BlazingPizza.Client/Program.cs | 46 +--- .../Shared/ConfiguredPizzaItem.razor | 2 +- .../BlazingPizza.ComponentsLibrary.csproj | 3 +- .../LocalStorage.cs | 26 +- .../Map/Map.razor | 8 +- .../Map/Marker.cs | 15 +- .../Map/Point.cs | 15 +- .../BlazingPizza.Server.csproj | 5 +- .../Extensions/OrderExtensions.cs | 17 +- src/BlazingPizza.Server/GlobalUsings.cs | 18 ++ .../Hubs/IOrderStatusHub.cs | 17 +- .../Hubs/OrderStatusHub.cs | 48 ++-- .../NotificationsController.cs | 72 ++--- .../OidcConfigurationController.cs | 40 +-- src/BlazingPizza.Server/OrdersController.cs | 259 +++++++++--------- src/BlazingPizza.Server/PizzaStoreContext.cs | 70 +++-- src/BlazingPizza.Server/PizzaStoreUser.cs | 13 +- src/BlazingPizza.Server/PizzasController.cs | 24 +- src/BlazingPizza.Server/Program.cs | 9 +- src/BlazingPizza.Server/SeedData.cs | 41 ++- .../Services/DefaultBackgroundOrderQueue.cs | 36 +-- .../Services/FakeOrderStatusService.cs | 126 ++++----- .../Services/IBackgroundOrderQueue.cs | 17 +- src/BlazingPizza.Server/SpecialsController.cs | 43 ++- src/BlazingPizza.Server/Startup.cs | 143 +++++----- src/BlazingPizza.Server/ToppingsController.cs | 8 +- src/BlazingPizza.Shared/Address.cs | 47 ++-- .../BlazingPizza.Shared.csproj | 3 +- src/BlazingPizza.Shared/LatLong.cs | 51 ++-- .../NotificationSubscription.cs | 29 +- src/BlazingPizza.Shared/Order.cs | 43 ++- .../OrderStatusHubConsts.cs | 21 +- src/BlazingPizza.Shared/OrderWithStatus.cs | 144 +++++----- src/BlazingPizza.Shared/Pizza.cs | 72 +++-- src/BlazingPizza.Shared/PizzaSpecial.cs | 37 ++- src/BlazingPizza.Shared/PizzaTopping.cs | 19 +- src/BlazingPizza.Shared/Topping.cs | 23 +- src/BlazingPizza.Shared/UserInfo.cs | 15 +- src/BlazingPizza.sln | 195 ++++++------- 53 files changed, 1240 insertions(+), 1193 deletions(-) create mode 100644 .editorconfig create mode 100644 src/BlazingPizza.Client/GlobalUsings.cs create mode 100644 src/BlazingPizza.Server/GlobalUsings.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..42cb5cc5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,192 @@ +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[project.json] +indent_size = 2 + +# Generated code +[*{_AssemblyInfo.cs,.notsupported.cs}] +generated_code = true + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_elsewhere = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Namespace preference +csharp_style_namespace_declarations = file_scoped + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +[*.{csproj,vbproj,proj,nativeproj,locproj}] +charset = utf-8 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd,bat}] +end_of_line = crlf diff --git a/Directory.Build.props b/Directory.Build.props index bb2df7b9..ce304b82 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,9 @@ - 5.0.0 - 5.0.0 - 5.0.0 - 5.0.0 + 6.0.0 + 6.0.0 + 6.0.0 + 6.0.0 + 6.0.0 diff --git a/src/BlazingComponents/BlazingComponents.csproj b/src/BlazingComponents/BlazingComponents.csproj index 6325ad64..91d70942 100644 --- a/src/BlazingComponents/BlazingComponents.csproj +++ b/src/BlazingComponents/BlazingComponents.csproj @@ -1,7 +1,8 @@  - net5.0 + net6.0 + true diff --git a/src/BlazingComponents/TemplatedList.razor b/src/BlazingComponents/TemplatedList.razor index 7961600e..fac9811c 100644 --- a/src/BlazingComponents/TemplatedList.razor +++ b/src/BlazingComponents/TemplatedList.razor @@ -1,17 +1,17 @@ @typeparam TItem -@if (items == null) +@if (_items is null) { @Loading } -else if (!items.Any()) +else if (!_items.Any()) { @Empty } else {
- @foreach (var item in items) + @foreach (var item in _items) {
@Item(item) @@ -20,17 +20,20 @@ else
} -@code { - IEnumerable items; - +@code { + IEnumerable _items = null!; + [Parameter] public Func>> Loader { get; set; } [Parameter] public RenderFragment Loading { get; set; } - [Parameter] public RenderFragment Empty { get; set; } + [Parameter] public RenderFragment Empty { get; set; } [Parameter] public RenderFragment Item { get; set; } - [Parameter] public string ListGroupClass { get; set; } + [Parameter] public string? ListGroupClass { get; set; } protected override async Task OnParametersSetAsync() - { - items = await Loader(); + { + if (Loader is not null) + { + _items = await Loader(); + } } } diff --git a/src/BlazingPizza.Client/BlazingPizza.Client.csproj b/src/BlazingPizza.Client/BlazingPizza.Client.csproj index b19daa6e..f57961dd 100644 --- a/src/BlazingPizza.Client/BlazingPizza.Client.csproj +++ b/src/BlazingPizza.Client/BlazingPizza.Client.csproj @@ -1,15 +1,16 @@  - net5.0 + net6.0 + true - - + + diff --git a/src/BlazingPizza.Client/GlobalUsings.cs b/src/BlazingPizza.Client/GlobalUsings.cs new file mode 100644 index 00000000..44fd643b --- /dev/null +++ b/src/BlazingPizza.Client/GlobalUsings.cs @@ -0,0 +1,7 @@ +global using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +global using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +global using BlazingPizza.Client; +global using System.Net.Http.Json; +global using Microsoft.JSInterop; +global using Microsoft.AspNetCore.Components; +global using Microsoft.AspNetCore.SignalR.Client; diff --git a/src/BlazingPizza.Client/JSRuntimeExtensions.cs b/src/BlazingPizza.Client/JSRuntimeExtensions.cs index 89da520c..d55f09dd 100644 --- a/src/BlazingPizza.Client/JSRuntimeExtensions.cs +++ b/src/BlazingPizza.Client/JSRuntimeExtensions.cs @@ -1,13 +1,9 @@ -using Microsoft.JSInterop; -using System.Threading.Tasks; - -namespace BlazingPizza.Client -{ - public static class JSRuntimeExtensions - { - public static ValueTask Confirm(this IJSRuntime jsRuntime, string message) - { - return jsRuntime.InvokeAsync("confirm", message); - } - } +namespace BlazingPizza.Client; + +public static class JSRuntimeExtensions +{ + public static ValueTask Confirm(this IJSRuntime jsRuntime, string message) + { + return jsRuntime.InvokeAsync("confirm", message); + } } diff --git a/src/BlazingPizza.Client/OrderState.cs b/src/BlazingPizza.Client/OrderState.cs index 5265ae39..04b16c67 100644 --- a/src/BlazingPizza.Client/OrderState.cs +++ b/src/BlazingPizza.Client/OrderState.cs @@ -1,55 +1,46 @@ -using System.Collections.Generic; - -namespace BlazingPizza.Client -{ - public class OrderState - { - public bool ShowingConfigureDialog { get; private set; } - - public Pizza ConfiguringPizza { get; private set; } - - public Order Order { get; private set; } = new Order(); - - public void ShowConfigurePizzaDialog(PizzaSpecial special) - { - ConfiguringPizza = new Pizza() - { - Special = special, - SpecialId = special.Id, - Size = Pizza.DefaultSize, - Toppings = new List(), - }; - - ShowingConfigureDialog = true; - } - - public void CancelConfigurePizzaDialog() - { - ConfiguringPizza = null; - ShowingConfigureDialog = false; - } - - public void ConfirmConfigurePizzaDialog() - { - Order.Pizzas.Add(ConfiguringPizza); - ConfiguringPizza = null; - - ShowingConfigureDialog = false; - } - - public void RemoveConfiguredPizza(Pizza pizza) - { - Order.Pizzas.Remove(pizza); - } - - public void ResetOrder() - { - Order = new Order(); - } - - public void ReplaceOrder(Order order) - { - Order = order; - } - } +namespace BlazingPizza.Client; + +public class OrderState +{ + public bool ShowingConfigureDialog { get; private set; } + + public Pizza ConfiguringPizza { get; private set; } = default!; + + public Order Order { get; private set; } = new(); + + public void ShowConfigurePizzaDialog(PizzaSpecial special) + { + ConfiguringPizza = new Pizza() + { + Special = special, + SpecialId = special.Id, + Size = Pizza.DefaultSize, + Toppings = new List(), + }; + + ShowingConfigureDialog = true; + } + + public void CancelConfigurePizzaDialog() + { + ConfiguringPizza = null; + ShowingConfigureDialog = false; + } + + public void ConfirmConfigurePizzaDialog() + { + if (ConfiguringPizza is not null) + { + Order.Pizzas.Add(ConfiguringPizza); + ConfiguringPizza = null; + } + + ShowingConfigureDialog = false; + } + + public void RemoveConfiguredPizza(Pizza pizza) => Order.Pizzas.Remove(pizza); + + public void ResetOrder() => Order = new Order(); + + public void ReplaceOrder(Order order) => Order = order; } diff --git a/src/BlazingPizza.Client/OrdersClient.cs b/src/BlazingPizza.Client/OrdersClient.cs index 01c63572..ca7cde72 100644 --- a/src/BlazingPizza.Client/OrdersClient.cs +++ b/src/BlazingPizza.Client/OrdersClient.cs @@ -1,41 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Json; -using System.Threading.Tasks; - -namespace BlazingPizza.Client -{ - public class OrdersClient - { - private readonly HttpClient httpClient; - - public OrdersClient(HttpClient httpClient) - { - this.httpClient = httpClient; - } - - public async Task> GetOrders() => - await httpClient.GetFromJsonAsync>("orders"); - - - public async Task GetOrder(int orderId) => - await httpClient.GetFromJsonAsync($"orders/{orderId}"); - - - public async Task PlaceOrder(Order order) - { - var response = await httpClient.PostAsJsonAsync("orders", order); - response.EnsureSuccessStatusCode(); - var orderId = await response.Content.ReadFromJsonAsync(); - return orderId; - } - - public async Task SubscribeToNotifications(NotificationSubscription subscription) - { - var response = await httpClient.PutAsJsonAsync("notifications/subscribe", subscription); - response.EnsureSuccessStatusCode(); - } - } +namespace BlazingPizza.Client; + +public class OrdersClient +{ + private readonly HttpClient _httpClient; + + public OrdersClient(HttpClient httpClient) => _httpClient = httpClient; + + public async Task> GetOrders() => + await _httpClient.GetFromJsonAsync>("orders"); + + + public async Task GetOrder(int orderId) => + await _httpClient.GetFromJsonAsync($"orders/{orderId}"); + + public async Task PlaceOrder(Order order) + { + var response = await _httpClient.PostAsJsonAsync("orders", order); + response.EnsureSuccessStatusCode(); + var orderId = await response.Content.ReadFromJsonAsync(); + return orderId; + } + + public async Task SubscribeToNotifications(NotificationSubscription subscription) + { + var response = await _httpClient.PutAsJsonAsync("notifications/subscribe", subscription); + response.EnsureSuccessStatusCode(); + } } diff --git a/src/BlazingPizza.Client/Pages/Authentication.razor b/src/BlazingPizza.Client/Pages/Authentication.razor index abed0b1f..954940bd 100644 --- a/src/BlazingPizza.Client/Pages/Authentication.razor +++ b/src/BlazingPizza.Client/Pages/Authentication.razor @@ -11,7 +11,7 @@ @code{ [Parameter] public string Action { get; set; } - public PizzaAuthenticationState RemoteAuthenticationState { get; set; } = new PizzaAuthenticationState(); + public PizzaAuthenticationState RemoteAuthenticationState { get; set; } = new(); protected override void OnInitialized() { @@ -24,7 +24,7 @@ private void RestorePizza(PizzaAuthenticationState pizzaState) { - if (pizzaState.Order != null) + if (pizzaState.Order is not null) { OrderState.ReplaceOrder(pizzaState.Order); } diff --git a/src/BlazingPizza.Client/Pages/Checkout.razor b/src/BlazingPizza.Client/Pages/Checkout.razor index 611f152c..0b82a161 100644 --- a/src/BlazingPizza.Client/Pages/Checkout.razor +++ b/src/BlazingPizza.Client/Pages/Checkout.razor @@ -19,7 +19,7 @@
- @@ -28,7 +28,7 @@ @code { - bool isSubmitting; + bool _isSubmitting; protected override void OnInitialized() { @@ -39,7 +39,7 @@ async Task RequestNotificationSubscriptionAsync() { var subscription = await JSRuntime.InvokeAsync("blazorPushNotifications.requestSubscription"); - if (subscription != null) + if (subscription is not null) { try { @@ -54,7 +54,7 @@ async Task PlaceOrder() { - isSubmitting = true; + _isSubmitting = true; try { diff --git a/src/BlazingPizza.Client/Pages/Index.razor b/src/BlazingPizza.Client/Pages/Index.razor index ca5ac084..c7ca5d87 100644 --- a/src/BlazingPizza.Client/Pages/Index.razor +++ b/src/BlazingPizza.Client/Pages/Index.razor @@ -6,9 +6,9 @@
    - @if (specials != null) + @if (_specials != null) { - @foreach (var special in specials) + @foreach (var special in _specials) {
  • @@ -56,17 +56,18 @@ @code { - List specials; + List _specials; Order Order => OrderState.Order; protected override async Task OnInitializedAsync() { - specials = await HttpClient.GetFromJsonAsync>("specials"); + _specials = await HttpClient.GetFromJsonAsync>("specials"); } async Task RemovePizza(Pizza configuredPizza) { - if (await JS.Confirm($"Remove {configuredPizza.Special.Name} pizza from the order?")) + if (configuredPizza.Special is not null && + await JS.Confirm($"Remove {configuredPizza.Special.Name} pizza from the order?")) { OrderState.RemoveConfiguredPizza(configuredPizza); } diff --git a/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs b/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs index 3143846a..1193d589 100644 --- a/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs +++ b/src/BlazingPizza.Client/Pages/OrderDetails.razor.cs @@ -1,102 +1,97 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.WebAssembly.Authentication; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Threading.Tasks; +namespace BlazingPizza.Client.Pages; -namespace BlazingPizza.Client.Pages +public sealed partial class OrderDetails : IAsyncDisposable { - public sealed partial class OrderDetails : IAsyncDisposable - { - private HubConnection _hubConnection; - private OrderWithStatus _orderWithStatus; - private bool _invalidOrder; - - [Parameter] public int OrderId { get; set; } + private HubConnection _hubConnection; + private OrderWithStatus _orderWithStatus; + private bool _invalidOrder; - [Inject] public NavigationManager Nav { get; set; } + [Parameter] public int OrderId { get; set; } - [Inject] public OrdersClient OrdersClient { get; set; } + [Inject] public NavigationManager Nav { get; set; } = default!; + [Inject] public OrdersClient OrdersClient { get; set; } = default!; + [Inject] public IAccessTokenProvider AccessTokenProvider { get; set; } = default!; - [Inject] public IAccessTokenProvider AccessTokenProvider { get; set; } - - protected override async Task OnInitializedAsync() - { - _hubConnection = new HubConnectionBuilder() - .WithUrl( - Nav.ToAbsoluteUri("/orderstatus"), - options => options.AccessTokenProvider = GetAccessTokenValueAsync) - .WithAutomaticReconnect() - .AddMessagePackProtocol() - .Build(); + protected override async Task OnInitializedAsync() + { + _hubConnection = new HubConnectionBuilder() + .WithUrl( + Nav.ToAbsoluteUri("/orderstatus"), + options => options.AccessTokenProvider = GetAccessTokenValueAsync) + .WithAutomaticReconnect() + .AddMessagePackProtocol() + .Build(); - _hubConnection.On( - OrderStatusHubConsts.EventNames.OrderStatusChanged, OnOrderStatusChangedAsync); + _hubConnection.On( + OrderStatusHubConsts.EventNames.OrderStatusChanged, OnOrderStatusChangedAsync); - await _hubConnection.StartAsync(); - } + await _hubConnection.StartAsync(); + } - private async Task GetAccessTokenValueAsync() - { - var result = await AccessTokenProvider.RequestAccessToken(); - return result.TryGetToken(out var accessToken) - ? accessToken.Value - : null; - } + private async Task GetAccessTokenValueAsync() + { + var result = await AccessTokenProvider.RequestAccessToken(); + return result.TryGetToken(out var accessToken) + ? accessToken.Value + : null; + } - protected override async Task OnParametersSetAsync() + protected override async Task OnParametersSetAsync() + { + try { - try + _orderWithStatus = await OrdersClient.GetOrder(OrderId); + if (_orderWithStatus is null || _hubConnection is null) { - _orderWithStatus = await OrdersClient.GetOrder(OrderId); - - StateHasChanged(); - - if (_orderWithStatus.IsDelivered) - { - await _hubConnection.InvokeAsync( - OrderStatusHubConsts.MethodNames.StopTrackingOrder, _orderWithStatus.Order); - await _hubConnection.StopAsync(); - } - else - { - await _hubConnection.InvokeAsync( - OrderStatusHubConsts.MethodNames.StartTrackingOrder, _orderWithStatus.Order); - } + return; } - catch (AccessTokenNotAvailableException ex) + + if (_orderWithStatus.IsDelivered) { - ex.Redirect(); + await _hubConnection.InvokeAsync( + OrderStatusHubConsts.MethodNames.StopTrackingOrder, _orderWithStatus.Order); + await _hubConnection.StopAsync(); } - catch (Exception ex) + else { - _invalidOrder = true; - Console.Error.WriteLine(ex); - StateHasChanged(); + await _hubConnection.InvokeAsync( + OrderStatusHubConsts.MethodNames.StartTrackingOrder, _orderWithStatus.Order); } } + catch (AccessTokenNotAvailableException ex) + { + ex.Redirect(); + } + catch (Exception ex) + { + _invalidOrder = true; + Console.Error.WriteLine(ex); + } + finally + { + StateHasChanged(); + } + } - private Task OnOrderStatusChangedAsync(OrderWithStatus orderWithStatus) => - InvokeAsync(() => - { - _orderWithStatus = orderWithStatus; - StateHasChanged(); - }); + private Task OnOrderStatusChangedAsync(OrderWithStatus orderWithStatus) => + InvokeAsync(() => + { + _orderWithStatus = orderWithStatus; + StateHasChanged(); + }); - public async ValueTask DisposeAsync() + public async ValueTask DisposeAsync() + { + if (_hubConnection is not null) { - if (_hubConnection is not null) + if (_orderWithStatus is not null) { - if (_orderWithStatus is not null) - { - await _hubConnection.InvokeAsync( - OrderStatusHubConsts.MethodNames.StopTrackingOrder, _orderWithStatus.Order); - } - - await _hubConnection.DisposeAsync(); - _hubConnection = null; + await _hubConnection.InvokeAsync( + OrderStatusHubConsts.MethodNames.StopTrackingOrder, _orderWithStatus.Order); } + + await _hubConnection.DisposeAsync(); + _hubConnection = null; } } } diff --git a/src/BlazingPizza.Client/PizzaAuthenticationState.cs b/src/BlazingPizza.Client/PizzaAuthenticationState.cs index 3f295928..4598db69 100644 --- a/src/BlazingPizza.Client/PizzaAuthenticationState.cs +++ b/src/BlazingPizza.Client/PizzaAuthenticationState.cs @@ -1,9 +1,6 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Authentication; - -namespace BlazingPizza.Client -{ - public class PizzaAuthenticationState : RemoteAuthenticationState - { - public Order Order { get; set; } - } +namespace BlazingPizza.Client; + +public class PizzaAuthenticationState : RemoteAuthenticationState +{ + public Order Order { get; set; } } diff --git a/src/BlazingPizza.Client/Program.cs b/src/BlazingPizza.Client/Program.cs index afeee128..638a3a9c 100644 --- a/src/BlazingPizza.Client/Program.cs +++ b/src/BlazingPizza.Client/Program.cs @@ -1,31 +1,15 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Authentication; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Net.Http; -using System.Threading.Tasks; - -namespace BlazingPizza.Client -{ - public class Program - { - public static async Task Main(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.RootComponents.Add("#app"); - - builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - builder.Services.AddHttpClient(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) - .AddHttpMessageHandler(); - builder.Services.AddScoped(); - - // Add auth services - builder.Services.AddApiAuthorization(options => - { - options.AuthenticationPaths.LogOutSucceededPath = ""; - }); - - await builder.Build().RunAsync(); - } - } -} +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); + +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Services.AddHttpClient(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) + .AddHttpMessageHandler(); +builder.Services.AddScoped(); + +// Add auth services +builder.Services.AddApiAuthorization(options => +{ + options.AuthenticationPaths.LogOutSucceededPath = ""; +}); + +await builder.Build().RunAsync(); diff --git a/src/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor b/src/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor index e0e9f41d..7d02fa48 100644 --- a/src/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor +++ b/src/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor @@ -8,7 +8,7 @@ }
- @Pizza.GetFormattedTotalPrice() + @Pizza?.GetFormattedTotalPrice()
diff --git a/src/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj b/src/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj index 5f51ee3c..db33f675 100644 --- a/src/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj +++ b/src/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj @@ -1,7 +1,8 @@  - net5.0 + net6.0 + true diff --git a/src/BlazingPizza.ComponentsLibrary/LocalStorage.cs b/src/BlazingPizza.ComponentsLibrary/LocalStorage.cs index f36b098e..16e4a13d 100644 --- a/src/BlazingPizza.ComponentsLibrary/LocalStorage.cs +++ b/src/BlazingPizza.ComponentsLibrary/LocalStorage.cs @@ -1,17 +1,15 @@ using Microsoft.JSInterop; -using System.Threading.Tasks; -namespace BlazingPizza.ComponentsLibrary -{ - public static class LocalStorage - { - public static ValueTask GetAsync(IJSRuntime jsRuntime, string key) - => jsRuntime.InvokeAsync("blazorLocalStorage.get", key); - - public static ValueTask SetAsync(IJSRuntime jsRuntime, string key, object value) - => jsRuntime.InvokeVoidAsync("blazorLocalStorage.set", key, value); - - public static ValueTask DeleteAsync(IJSRuntime jsRuntime, string key) - => jsRuntime.InvokeVoidAsync("blazorLocalStorage.delete", key); - } +namespace BlazingPizza.ComponentsLibrary; + +public static class LocalStorage +{ + public static ValueTask GetAsync(IJSRuntime jsRuntime, string key) + => jsRuntime.InvokeAsync("blazorLocalStorage.get", key); + + public static ValueTask SetAsync(IJSRuntime jsRuntime, string key, object value) + => jsRuntime.InvokeVoidAsync("blazorLocalStorage.set", key, value); + + public static ValueTask DeleteAsync(IJSRuntime jsRuntime, string key) + => jsRuntime.InvokeVoidAsync("blazorLocalStorage.delete", key); } diff --git a/src/BlazingPizza.ComponentsLibrary/Map/Map.razor b/src/BlazingPizza.ComponentsLibrary/Map/Map.razor index c2413ac4..50248bb3 100644 --- a/src/BlazingPizza.ComponentsLibrary/Map/Map.razor +++ b/src/BlazingPizza.ComponentsLibrary/Map/Map.razor @@ -3,11 +3,11 @@
-@code { +@code { string elementId = $"map-{Guid.NewGuid().ToString("D")}"; - - [Parameter] public double Zoom { get; set; } - [Parameter] public List Markers { get; set; } + + [Parameter] public double Zoom { get; set; } + [Parameter] public List Markers { get; set; } = default!; protected async override Task OnAfterRenderAsync(bool firstRender) { diff --git a/src/BlazingPizza.ComponentsLibrary/Map/Marker.cs b/src/BlazingPizza.ComponentsLibrary/Map/Marker.cs index df182426..155822d3 100644 --- a/src/BlazingPizza.ComponentsLibrary/Map/Marker.cs +++ b/src/BlazingPizza.ComponentsLibrary/Map/Marker.cs @@ -1,9 +1,8 @@ -namespace BlazingPizza.ComponentsLibrary.Map -{ - public class Marker : Point - { - public string Description { get; set; } - - public bool ShowPopup { get; set; } - } +namespace BlazingPizza.ComponentsLibrary.Map; + +public class Marker : Point +{ + public string Description { get; set; } + + public bool ShowPopup { get; set; } } diff --git a/src/BlazingPizza.ComponentsLibrary/Map/Point.cs b/src/BlazingPizza.ComponentsLibrary/Map/Point.cs index 03841826..0d90dbb6 100644 --- a/src/BlazingPizza.ComponentsLibrary/Map/Point.cs +++ b/src/BlazingPizza.ComponentsLibrary/Map/Point.cs @@ -1,9 +1,8 @@ -namespace BlazingPizza.ComponentsLibrary.Map -{ - public class Point - { - public double X { get; set; } - - public double Y { get; set; } - } +namespace BlazingPizza.ComponentsLibrary.Map; + +public class Point +{ + public double X { get; set; } + + public double Y { get; set; } } diff --git a/src/BlazingPizza.Server/BlazingPizza.Server.csproj b/src/BlazingPizza.Server/BlazingPizza.Server.csproj index f78b0ee7..1b57ed5c 100644 --- a/src/BlazingPizza.Server/BlazingPizza.Server.csproj +++ b/src/BlazingPizza.Server/BlazingPizza.Server.csproj @@ -1,7 +1,8 @@  - net5.0 + net6.0 + true @@ -11,7 +12,7 @@ - + diff --git a/src/BlazingPizza.Server/Extensions/OrderExtensions.cs b/src/BlazingPizza.Server/Extensions/OrderExtensions.cs index a16b01ec..30657552 100644 --- a/src/BlazingPizza.Server/Extensions/OrderExtensions.cs +++ b/src/BlazingPizza.Server/Extensions/OrderExtensions.cs @@ -1,11 +1,10 @@ -namespace BlazingPizza.Server.Extensions +namespace BlazingPizza.Server.Extensions; + +internal static class OrderExtensions { - internal static class OrderExtensions - { - /// - /// Creates a unique identifier for the given instance. - /// - internal static string ToOrderTrackingGroupId(this Order order) => - $"{order.OrderId}:{order.UserId}:{order.CreatedTime.Ticks}"; - } + /// + /// Creates a unique identifier for the given instance. + /// + internal static string ToOrderTrackingGroupId(this Order order) => + $"{order.OrderId}:{order.UserId}:{order.CreatedTime.Ticks}"; } diff --git a/src/BlazingPizza.Server/GlobalUsings.cs b/src/BlazingPizza.Server/GlobalUsings.cs new file mode 100644 index 00000000..063af808 --- /dev/null +++ b/src/BlazingPizza.Server/GlobalUsings.cs @@ -0,0 +1,18 @@ +global using System.Diagnostics.CodeAnalysis; +global using System.Security.Claims; +global using System.Text.Json; +global using System.Threading.Channels; +global using BlazingPizza.Server; +global using BlazingPizza.Server.Extensions; +global using BlazingPizza.Server.Hubs; +global using BlazingPizza.Server.Services; +global using Duende.IdentityServer.EntityFramework.Options; +global using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +global using Microsoft.AspNetCore.Authentication; +global using Microsoft.AspNetCore.Authorization; +global using Microsoft.AspNetCore.Identity; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.AspNetCore.SignalR; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.Options; +global using WebPush; diff --git a/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs b/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs index 3e118f08..5e5fef7e 100644 --- a/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs +++ b/src/BlazingPizza.Server/Hubs/IOrderStatusHub.cs @@ -1,13 +1,10 @@ -using System.Threading.Tasks; +namespace BlazingPizza.Server.Hubs; -namespace BlazingPizza.Server.Hubs +public interface IOrderStatusHub { - public interface IOrderStatusHub - { - /// - /// This event name should match , - /// which is shared with clients for discoverability. - /// - Task OrderStatusChanged(OrderWithStatus order); - } + /// + /// This event name should match , + /// which is shared with clients for discoverability. + /// + Task OrderStatusChanged(OrderWithStatus order); } diff --git a/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs b/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs index 3117be36..22995697 100644 --- a/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs +++ b/src/BlazingPizza.Server/Hubs/OrderStatusHub.cs @@ -1,31 +1,25 @@ -using BlazingPizza.Server.Extensions; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.SignalR; -using System.Threading.Tasks; +namespace BlazingPizza.Server.Hubs; -namespace BlazingPizza.Server.Hubs +[Authorize] +public class OrderStatusHub : Hub { - [Authorize] - public class OrderStatusHub : Hub - { - /// - /// Adds the current connection to the order's unique group identifier, where - /// order status changes will be notified in real-time. - /// This method name should match , - /// which is shared with clients for discoverability. - /// - public Task StartTrackingOrder(Order order) => - Groups.AddToGroupAsync( - Context.ConnectionId, order.ToOrderTrackingGroupId()); + /// + /// Adds the current connection to the order's unique group identifier, where + /// order status changes will be notified in real-time. + /// This method name should match , + /// which is shared with clients for discoverability. + /// + public Task StartTrackingOrder(Order order) => + Groups.AddToGroupAsync( + Context.ConnectionId, order.ToOrderTrackingGroupId()); - /// - /// Removes the current connection from the order's unique group identifier, - /// ending real-time change updates for this order. - /// This method name should match , - /// which is shared with clients for discoverability. - /// - public Task StopTrackingOrder(Order order) => - Groups.RemoveFromGroupAsync( - Context.ConnectionId, order.ToOrderTrackingGroupId()); - } + /// + /// Removes the current connection from the order's unique group identifier, + /// ending real-time change updates for this order. + /// This method name should match , + /// which is shared with clients for discoverability. + /// + public Task StopTrackingOrder(Order order) => + Groups.RemoveFromGroupAsync( + Context.ConnectionId, order.ToOrderTrackingGroupId()); } diff --git a/src/BlazingPizza.Server/NotificationsController.cs b/src/BlazingPizza.Server/NotificationsController.cs index bb3b96ff..074de191 100644 --- a/src/BlazingPizza.Server/NotificationsController.cs +++ b/src/BlazingPizza.Server/NotificationsController.cs @@ -1,43 +1,31 @@ -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace BlazingPizza.Server -{ - [Route("notifications")] - [ApiController] - [Authorize] - public class NotificationsController : Controller - { - private readonly PizzaStoreContext _db; - - public NotificationsController(PizzaStoreContext db) - { - _db = db; - } - - [HttpPut("subscribe")] - public async Task Subscribe(NotificationSubscription subscription) - { - // We're storing at most one subscription per user, so delete old ones. - // Alternatively, you could let the user register multiple subscriptions from different browsers/devices. - var userId = GetUserId(); - var oldSubscriptions = _db.NotificationSubscriptions.Where(e => e.UserId == userId); - _db.NotificationSubscriptions.RemoveRange(oldSubscriptions); - - // Store new subscription - subscription.UserId = userId; - _db.NotificationSubscriptions.Attach(subscription); - - await _db.SaveChangesAsync(); - return subscription; - } - - private string GetUserId() - { - return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - } - } +namespace BlazingPizza.Server; + +[Route("notifications")] +[ApiController] +[Authorize] +public class NotificationsController : Controller +{ + private readonly PizzaStoreContext _db; + + public NotificationsController(PizzaStoreContext db) => _db = db; + + [HttpPut("subscribe")] + public async Task Subscribe(NotificationSubscription subscription) + { + // We're storing at most one subscription per user, so delete old ones. + // Alternatively, you could let the user register multiple subscriptions from different browsers/devices. + var userId = GetUserId(); + var oldSubscriptions = _db.NotificationSubscriptions.Where(e => e.UserId == userId); + _db.NotificationSubscriptions.RemoveRange(oldSubscriptions); + + // Store new subscription + subscription.UserId = userId; + _db.NotificationSubscriptions.Attach(subscription); + + await _db.SaveChangesAsync(); + return subscription; + } + + private string GetUserId() => + HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); } diff --git a/src/BlazingPizza.Server/OidcConfigurationController.cs b/src/BlazingPizza.Server/OidcConfigurationController.cs index c074debf..cab35d0f 100644 --- a/src/BlazingPizza.Server/OidcConfigurationController.cs +++ b/src/BlazingPizza.Server/OidcConfigurationController.cs @@ -1,26 +1,16 @@ -using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace BlazingPizza.Server -{ - public class OidcConfigurationController : Controller - { - public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider) - { - ClientRequestParametersProvider = clientRequestParametersProvider; - } - - public IClientRequestParametersProvider ClientRequestParametersProvider { get; } - - [HttpGet("_configuration/{clientId}")] - public IActionResult GetClientRequestParameters([FromRoute]string clientId) - { - var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId); - return Ok(parameters); - } - } +namespace BlazingPizza.Server; + +public class OidcConfigurationController : Controller +{ + public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider) => + ClientRequestParametersProvider = clientRequestParametersProvider; + + public IClientRequestParametersProvider ClientRequestParametersProvider { get; } + + [HttpGet("_configuration/{clientId}")] + public IActionResult GetClientRequestParameters([FromRoute] string clientId) + { + var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId); + return Ok(parameters); + } } diff --git a/src/BlazingPizza.Server/OrdersController.cs b/src/BlazingPizza.Server/OrdersController.cs index 9ae26541..3adced4b 100644 --- a/src/BlazingPizza.Server/OrdersController.cs +++ b/src/BlazingPizza.Server/OrdersController.cs @@ -1,140 +1,125 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Text.Json; -using System.Threading.Tasks; -using BlazingPizza.Server.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using WebPush; - -namespace BlazingPizza.Server -{ - [Route("orders")] - [ApiController] - [Authorize] - public class OrdersController : Controller - { - private readonly PizzaStoreContext _db; - private readonly IBackgroundOrderQueue _orderQueue; - - public OrdersController( - PizzaStoreContext db, IBackgroundOrderQueue orderQueue) => - (_db, _orderQueue) = (db, orderQueue); - - [HttpGet] - public async Task>> GetOrders() - { - var orders = await _db.Orders - .Where(o => o.UserId == GetUserId()) - .Include(o => o.DeliveryLocation) - .Include(o => o.Pizzas).ThenInclude(p => p.Special) - .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) - .OrderByDescending(o => o.CreatedTime) - .ToListAsync(); - - return orders.Select(OrderWithStatus.FromOrder).ToList(); - } - - [HttpGet("{orderId}")] - public async Task> GetOrderWithStatus(int orderId) - { - var order = await _db.Orders - .Where(o => o.OrderId == orderId) - .Where(o => o.UserId == GetUserId()) - .Include(o => o.DeliveryLocation) - .Include(o => o.Pizzas).ThenInclude(p => p.Special) - .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) - .SingleOrDefaultAsync(); - - return order is null ? NotFound() : OrderWithStatus.FromOrder(order); - } - - [HttpPost] - public async Task> PlaceOrder(Order order) - { - order.CreatedTime = DateTime.Now; - order.DeliveryLocation = new LatLong(51.5001, -0.1239); - order.UserId = GetUserId(); - - // Enforce existence of Pizza.SpecialId and Topping.ToppingId - // in the database - prevent the submitter from making up - // new specials and toppings - foreach (var pizza in order.Pizzas) - { - pizza.SpecialId = pizza.Special.Id; - pizza.Special = null; - - foreach (var topping in pizza.Toppings) - { - topping.ToppingId = topping.Topping.Id; - topping.Topping = null; - } - } - - _db.Orders.Attach(order); - await _db.SaveChangesAsync(); - - // In the background, send push notifications if possible - var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); - if (subscription != null) - { - await QueueNotificationsAsync(order, subscription); - } - - return order.OrderId; - } - - private string GetUserId() - { - return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - } - - private async Task QueueNotificationsAsync( - Order order, NotificationSubscription subscription) - { - // In a realistic case, some other backend process would track - // order delivery progress and send us notifications when it - // changes. Since we don't have any such process here, fake it. - await _orderQueue.QueueBackgroundOrderStatusAsync(async canellationToken => +namespace BlazingPizza.Server; + +[Route("orders")] +[ApiController] +[Authorize] +public class OrdersController : Controller +{ + private readonly PizzaStoreContext _db; + private readonly IBackgroundOrderQueue _orderQueue; + + public OrdersController( + PizzaStoreContext db, IBackgroundOrderQueue orderQueue) => + (_db, _orderQueue) = (db, orderQueue); + + [HttpGet] + public async Task>> GetOrders() + { + var orders = await _db.Orders + .Where(o => o.UserId == GetUserId()) + .Include(o => o.DeliveryLocation) + .Include(o => o.Pizzas).ThenInclude(p => p.Special) + .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) + .OrderByDescending(o => o.CreatedTime) + .ToListAsync(); + + return orders.Select(OrderWithStatus.FromOrder).ToList(); + } + + [HttpGet("{orderId}")] + public async Task> GetOrderWithStatus(int orderId) + { + var order = await _db.Orders + .Where(o => o.OrderId == orderId) + .Where(o => o.UserId == GetUserId()) + .Include(o => o.DeliveryLocation) + .Include(o => o.Pizzas).ThenInclude(p => p.Special) + .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) + .SingleOrDefaultAsync(); + + return order is null ? NotFound() : OrderWithStatus.FromOrder(order); + } + + [HttpPost] + public async Task> PlaceOrder(Order order) + { + order.CreatedTime = DateTime.Now; + order.DeliveryLocation = new LatLong(51.5001, -0.1239); + order.UserId = GetUserId(); + + // Enforce existence of Pizza.SpecialId and Topping.ToppingId + // in the database - prevent the submitter from making up + // new specials and toppings + foreach (var pizza in order.Pizzas) + { + pizza.SpecialId = pizza.Special.Id; + pizza.Special = null; + + foreach (var topping in pizza.Toppings) { - await Task.Delay(OrderWithStatus.PreparationDuration, canellationToken); - return await SendNotificationAsync(order, subscription, "Your order has been dispatched!"); - }); - - await _orderQueue.QueueBackgroundOrderStatusAsync(async canellationToken => + topping.ToppingId = topping.Topping.Id; + topping.Topping = null; + } + } + + _db.Orders.Attach(order); + await _db.SaveChangesAsync(); + + // In the background, send push notifications if possible + var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); + if (subscription is not null) + { + await QueueNotificationsAsync(order, subscription); + } + + return order.OrderId; + } + + private string GetUserId() => + HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); + + private async Task QueueNotificationsAsync( + Order order, NotificationSubscription subscription) + { + // In a realistic case, some other backend process would track + // order delivery progress and send us notifications when it + // changes. Since we don't have any such process here, fake it. + await _orderQueue.QueueBackgroundOrderStatusAsync(async canellationToken => + { + await Task.Delay(OrderWithStatus.PreparationDuration, canellationToken); + return await SendNotificationAsync(order, subscription, "Your order has been dispatched!"); + }); + + await _orderQueue.QueueBackgroundOrderStatusAsync(async canellationToken => + { + await Task.Delay(OrderWithStatus.DeliveryDuration, canellationToken); + return await SendNotificationAsync(order, subscription, "Your order is now delivered. Enjoy!"); + }); + } + + private static async Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message) + { + // For a real application, generate your own + var publicKey = "BLC8GOevpcpjQiLkO7JmVClQjycvTCYWm6Cq_a7wJZlstGTVZvwGFFHMYfXt6Njyvgx_GlXJeo5cSiZ1y4JOx1o"; + var privateKey = "OrubzSz3yWACscZXjFQrrtDwCKg-TGFuWhluQ2wLXDo"; + + var pushSubscription = new PushSubscription(subscription.Url, subscription.P256dh, subscription.Auth); + var vapidDetails = new VapidDetails("mailto:", publicKey, privateKey); + var webPushClient = new WebPushClient(); + try + { + var payload = JsonSerializer.Serialize(new { - await Task.Delay(OrderWithStatus.DeliveryDuration, canellationToken); - return await SendNotificationAsync(order, subscription, "Your order is now delivered. Enjoy!"); - }); - } - - private static async Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message) - { - // For a real application, generate your own - var publicKey = "BLC8GOevpcpjQiLkO7JmVClQjycvTCYWm6Cq_a7wJZlstGTVZvwGFFHMYfXt6Njyvgx_GlXJeo5cSiZ1y4JOx1o"; - var privateKey = "OrubzSz3yWACscZXjFQrrtDwCKg-TGFuWhluQ2wLXDo"; - - var pushSubscription = new PushSubscription(subscription.Url, subscription.P256dh, subscription.Auth); - var vapidDetails = new VapidDetails("mailto:", publicKey, privateKey); - var webPushClient = new WebPushClient(); - try - { - var payload = JsonSerializer.Serialize(new - { - message, - url = $"myorders/{order.OrderId}", - }); - await webPushClient.SendNotificationAsync(pushSubscription, payload, vapidDetails); - } - catch (Exception ex) - { - Console.Error.WriteLine($"Error sending push notification: {ex.Message}"); - } - - return order; - } - } + message, + url = $"myorders/{order.OrderId}", + }); + await webPushClient.SendNotificationAsync(pushSubscription, payload, vapidDetails); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error sending push notification: {ex.Message}"); + } + + return order; + } } diff --git a/src/BlazingPizza.Server/PizzaStoreContext.cs b/src/BlazingPizza.Server/PizzaStoreContext.cs index 51e96e1e..6823c0fd 100644 --- a/src/BlazingPizza.Server/PizzaStoreContext.cs +++ b/src/BlazingPizza.Server/PizzaStoreContext.cs @@ -1,39 +1,33 @@ -using IdentityServer4.EntityFramework.Options; -using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; - -namespace BlazingPizza.Server -{ - public class PizzaStoreContext : ApiAuthorizationDbContext - { - public PizzaStoreContext( - DbContextOptions options, - IOptions operationalStoreOptions) : base(options, operationalStoreOptions) - { - } - - public DbSet Orders { get; set; } - - public DbSet Pizzas { get; set; } - - public DbSet Specials { get; set; } - - public DbSet Toppings { get; set; } - - public DbSet NotificationSubscriptions { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - // Configuring a many-to-many special -> topping relationship that is friendly for serialization - modelBuilder.Entity().HasKey(pst => new { pst.PizzaId, pst.ToppingId }); - modelBuilder.Entity().HasOne().WithMany(ps => ps.Toppings); - modelBuilder.Entity().HasOne(pst => pst.Topping).WithMany(); - - // Inline the Lat-Long pairs in Order rather than having a FK to another table - modelBuilder.Entity().OwnsOne(o => o.DeliveryLocation); - } - } +namespace BlazingPizza.Server; + +public class PizzaStoreContext : ApiAuthorizationDbContext +{ + public PizzaStoreContext( + DbContextOptions options, + IOptions operationalStoreOptions) : base(options, operationalStoreOptions) + { + } + + public DbSet Orders { get; set; } + + public DbSet Pizzas { get; set; } + + public DbSet Specials { get; set; } + + public DbSet Toppings { get; set; } + + public DbSet NotificationSubscriptions { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Configuring a many-to-many special -> topping relationship that is friendly for serialization + modelBuilder.Entity().HasKey(pst => new { pst.PizzaId, pst.ToppingId }); + modelBuilder.Entity().HasOne().WithMany(ps => ps.Toppings); + modelBuilder.Entity().HasOne(pst => pst.Topping).WithMany(); + + // Inline the Lat-Long pairs in Order rather than having a FK to another table + modelBuilder.Entity().OwnsOne(o => o.DeliveryLocation); + } } diff --git a/src/BlazingPizza.Server/PizzaStoreUser.cs b/src/BlazingPizza.Server/PizzaStoreUser.cs index 171ce83d..3843d6e2 100644 --- a/src/BlazingPizza.Server/PizzaStoreUser.cs +++ b/src/BlazingPizza.Server/PizzaStoreUser.cs @@ -1,8 +1,5 @@ -using Microsoft.AspNetCore.Identity; - -namespace BlazingPizza.Server -{ - public class PizzaStoreUser : IdentityUser - { - } -} \ No newline at end of file +namespace BlazingPizza.Server; + +public class PizzaStoreUser : IdentityUser +{ +} diff --git a/src/BlazingPizza.Server/PizzasController.cs b/src/BlazingPizza.Server/PizzasController.cs index c082552c..854b8506 100644 --- a/src/BlazingPizza.Server/PizzasController.cs +++ b/src/BlazingPizza.Server/PizzasController.cs @@ -1,16 +1,10 @@ -using Microsoft.AspNetCore.Mvc; - -namespace BlazingPizza.Server -{ - [Route("pizzas")] - [ApiController] - public class PizzasController : Controller - { - private readonly PizzaStoreContext _db; - - public PizzasController(PizzaStoreContext db) - { - _db = db; - } - } +namespace BlazingPizza.Server; + +[Route("pizzas")] +[ApiController] +public class PizzasController : Controller +{ + private readonly PizzaStoreContext _db; + + public PizzasController(PizzaStoreContext db) => _db = db; } diff --git a/src/BlazingPizza.Server/Program.cs b/src/BlazingPizza.Server/Program.cs index 31973c7b..1dff5448 100644 --- a/src/BlazingPizza.Server/Program.cs +++ b/src/BlazingPizza.Server/Program.cs @@ -1,9 +1,4 @@ -using BlazingPizza.Server; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -var host = Host.CreateDefaultBuilder(args) +var host = Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()) .Build(); @@ -18,4 +13,4 @@ } } -await host.RunAsync(); \ No newline at end of file +await host.RunAsync(); diff --git a/src/BlazingPizza.Server/SeedData.cs b/src/BlazingPizza.Server/SeedData.cs index 2a645ee6..4f135cdc 100644 --- a/src/BlazingPizza.Server/SeedData.cs +++ b/src/BlazingPizza.Server/SeedData.cs @@ -1,13 +1,11 @@ -using System.Threading.Tasks; +namespace BlazingPizza.Server; -namespace BlazingPizza.Server -{ - public static class SeedData - { - public static Task InitializeAsync(PizzaStoreContext db) +public static class SeedData +{ + public static Task InitializeAsync(PizzaStoreContext db) + { + var toppings = new Topping[] { - var toppings = new Topping[] - { new Topping { Name = "Extra cheese", @@ -117,11 +115,11 @@ public static Task InitializeAsync(PizzaStoreContext db) { Name = "Blue cheese", Price = 2.50m, - }, - }; - - var specials = new PizzaSpecial[] - { + }, + }; + + var specials = new PizzaSpecial[] + { new PizzaSpecial { Name = "Basic Cheese Pizza", @@ -184,13 +182,12 @@ public static Task InitializeAsync(PizzaStoreContext db) Description = "Traditional Italian pizza with tomatoes and basil", BasePrice = 9.99m, ImageUrl = "img/pizzas/margherita.jpg", - }, - }; - - db.Toppings.AddRange(toppings); - db.Specials.AddRange(specials); - - return db.SaveChangesAsync(); - } - } + }, + }; + + db.Toppings.AddRange(toppings); + db.Specials.AddRange(specials); + + return db.SaveChangesAsync(); + } } diff --git a/src/BlazingPizza.Server/Services/DefaultBackgroundOrderQueue.cs b/src/BlazingPizza.Server/Services/DefaultBackgroundOrderQueue.cs index bdc90c6d..4821755d 100644 --- a/src/BlazingPizza.Server/Services/DefaultBackgroundOrderQueue.cs +++ b/src/BlazingPizza.Server/Services/DefaultBackgroundOrderQueue.cs @@ -1,27 +1,21 @@ -using System; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; +namespace BlazingPizza.Server.Services; -namespace BlazingPizza.Server.Services +public class DefaultBackgroundOrderQueue : IBackgroundOrderQueue { - public class DefaultBackgroundOrderQueue : IBackgroundOrderQueue - { - private readonly Channel>> _queue; + private readonly Channel>> _queue; - public DefaultBackgroundOrderQueue() => - _queue = Channel.CreateBounded>>( - new BoundedChannelOptions(100) - { - FullMode = BoundedChannelFullMode.Wait - }); + public DefaultBackgroundOrderQueue() => + _queue = Channel.CreateBounded>>( + new BoundedChannelOptions(100) + { + FullMode = BoundedChannelFullMode.Wait + }); - public ValueTask QueueBackgroundOrderStatusAsync( - Func> workItem) => - _queue.Writer.WriteAsync(workItem); + public ValueTask QueueBackgroundOrderStatusAsync( + Func> workItem) => + _queue.Writer.WriteAsync(workItem); - public ValueTask>> DequeueAsync( - CancellationToken cancellationToken) => - _queue.Reader.ReadAsync(cancellationToken); - } + public ValueTask>> DequeueAsync( + CancellationToken cancellationToken) => + _queue.Reader.ReadAsync(cancellationToken); } diff --git a/src/BlazingPizza.Server/Services/FakeOrderStatusService.cs b/src/BlazingPizza.Server/Services/FakeOrderStatusService.cs index d2001493..f112106d 100644 --- a/src/BlazingPizza.Server/Services/FakeOrderStatusService.cs +++ b/src/BlazingPizza.Server/Services/FakeOrderStatusService.cs @@ -1,89 +1,75 @@ -using BlazingPizza.Server.Extensions; -using BlazingPizza.Server.Hubs; -using Microsoft.AspNetCore.SignalR; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +namespace BlazingPizza.Server.Services; -namespace BlazingPizza.Server.Services +public sealed class FakeOrderStatusService : BackgroundService { - public sealed class FakeOrderStatusService : BackgroundService - { - private readonly IBackgroundOrderQueue _orderQueue; - private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; + private readonly IBackgroundOrderQueue _orderQueue; + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; - public FakeOrderStatusService( - IBackgroundOrderQueue orderQueue, - IServiceProvider serviceProvider, - ILogger logger) => - (_orderQueue, _serviceProvider, _logger) = (orderQueue, serviceProvider, logger); + public FakeOrderStatusService( + IBackgroundOrderQueue orderQueue, + IServiceProvider serviceProvider, + ILogger logger) => + (_orderQueue, _serviceProvider, _logger) = (orderQueue, serviceProvider, logger); - [SuppressMessage( - "Style", - "IDE0063:Use simple 'using' statement", - Justification = "We need explicit disposal of the IServiceScope to avoid errant conditions.")] - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + [SuppressMessage( + "Style", + "IDE0063:Use simple 'using' statement", + Justification = "We need explicit disposal of the IServiceScope to avoid errant conditions.")] + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) { - while (!stoppingToken.IsCancellationRequested) + try { - try + var workItem = await _orderQueue.DequeueAsync(stoppingToken); + var order = await workItem(stoppingToken); + + using (var scope = _serviceProvider.CreateScope()) { - var workItem = await _orderQueue.DequeueAsync(stoppingToken); - var order = await workItem(stoppingToken); + var hubContext = + scope.ServiceProvider + .GetRequiredService>(); - using (var scope = _serviceProvider.CreateScope()) + // This is not production code, this is intended to act as a + // fake backend order processing system. DO NOT DO THIS. + var trackingOrderId = order.ToOrderTrackingGroupId(); + var orderWithStatus = await GetOrderAsync(scope.ServiceProvider, order.OrderId); + while (!orderWithStatus.IsDelivered) { - var hubContext = - scope.ServiceProvider - .GetRequiredService>(); - - // This is not production code, this is intended to act as a - // fake backend order processing system. DO NOT DO THIS. - var trackingOrderId = order.ToOrderTrackingGroupId(); - var orderWithStatus = await GetOrderAsync(scope.ServiceProvider, order.OrderId); - while (!orderWithStatus.IsDelivered) - { - await hubContext.Clients.Group(trackingOrderId).OrderStatusChanged(orderWithStatus); - await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken); - - orderWithStatus = OrderWithStatus.FromOrder(orderWithStatus.Order); - } - - // Send final delivery status update. await hubContext.Clients.Group(trackingOrderId).OrderStatusChanged(orderWithStatus); + await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken); + + orderWithStatus = OrderWithStatus.FromOrder(orderWithStatus.Order); } + + // Send final delivery status update. + await hubContext.Clients.Group(trackingOrderId).OrderStatusChanged(orderWithStatus); } - catch (OperationCanceledException) - { - // Prevent throwing if stoppingToken was signaled - } - catch (Exception ex) - { - _logger.LogError(ex, "Error occurred executing task work item."); - } + } + catch (OperationCanceledException) + { + // Prevent throwing if stoppingToken was signaled + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occurred executing task work item."); } } + } - static async Task GetOrderAsync(IServiceProvider serviceProvider, int orderId) - { - var pizzeStoreContext = - serviceProvider.GetRequiredService(); + static async Task GetOrderAsync(IServiceProvider serviceProvider, int orderId) + { + var pizzeStoreContext = + serviceProvider.GetRequiredService(); - var order = await pizzeStoreContext.Orders - .Where(o => o.OrderId == orderId) - .Include(o => o.DeliveryLocation) - .Include(o => o.Pizzas).ThenInclude(p => p.Special) - .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) - .SingleOrDefaultAsync(); + var order = await pizzeStoreContext.Orders + .Where(o => o.OrderId == orderId) + .Include(o => o.DeliveryLocation) + .Include(o => o.Pizzas).ThenInclude(p => p.Special) + .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) + .SingleOrDefaultAsync(); - return OrderWithStatus.FromOrder(order); - } + return OrderWithStatus.FromOrder(order); } } diff --git a/src/BlazingPizza.Server/Services/IBackgroundOrderQueue.cs b/src/BlazingPizza.Server/Services/IBackgroundOrderQueue.cs index d08bde73..6fc2670c 100644 --- a/src/BlazingPizza.Server/Services/IBackgroundOrderQueue.cs +++ b/src/BlazingPizza.Server/Services/IBackgroundOrderQueue.cs @@ -1,15 +1,10 @@ -using System; -using System.Threading; -using System.Threading.Tasks; +namespace BlazingPizza.Server.Services; -namespace BlazingPizza.Server.Services +public interface IBackgroundOrderQueue { - public interface IBackgroundOrderQueue - { - ValueTask QueueBackgroundOrderStatusAsync( - Func> workItem); + ValueTask QueueBackgroundOrderStatusAsync( + Func> workItem); - ValueTask>> DequeueAsync( - CancellationToken cancellationToken); - } + ValueTask>> DequeueAsync( + CancellationToken cancellationToken); } diff --git a/src/BlazingPizza.Server/SpecialsController.cs b/src/BlazingPizza.Server/SpecialsController.cs index a176844c..f5ccb57b 100644 --- a/src/BlazingPizza.Server/SpecialsController.cs +++ b/src/BlazingPizza.Server/SpecialsController.cs @@ -1,26 +1,19 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace BlazingPizza.Server -{ - [Route("specials")] - [ApiController] - public class SpecialsController : Controller - { - private readonly PizzaStoreContext _db; - - public SpecialsController(PizzaStoreContext db) - { - _db = db; - } - - [HttpGet] - public async Task>> GetSpecials() - { - return (await _db.Specials.ToListAsync()).OrderByDescending(s => s.BasePrice).ToList(); - } - } +namespace BlazingPizza.Server; + +[Route("specials")] +[ApiController] +public class SpecialsController : Controller +{ + private readonly PizzaStoreContext _db; + + public SpecialsController(PizzaStoreContext db) + { + _db = db; + } + + [HttpGet] + public async Task>> GetSpecials() + { + return (await _db.Specials.ToListAsync()).OrderByDescending(s => s.BasePrice).ToList(); + } } diff --git a/src/BlazingPizza.Server/Startup.cs b/src/BlazingPizza.Server/Startup.cs index 90859a3c..0114d9f5 100644 --- a/src/BlazingPizza.Server/Startup.cs +++ b/src/BlazingPizza.Server/Startup.cs @@ -1,79 +1,68 @@ -using BlazingPizza.Server.Hubs; -using BlazingPizza.Server.Services; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace BlazingPizza.Server -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) +namespace BlazingPizza.Server; + +public class Startup +{ + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddControllersWithViews(); + services.AddRazorPages(); + + services.AddDbContext(options => + options.UseSqlite("Data Source=pizza.db")); + + services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores(); + + services.AddIdentityServer() + .AddApiAuthorization(); + + services.AddAuthentication() + .AddIdentityServerJwt(); + + services.AddSignalR(options => options.EnableDetailedErrors = true) + .AddMessagePackProtocol(); + + services.AddHostedService(); + services.AddSingleton(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) { - services.AddControllersWithViews(); - services.AddRazorPages(); - - services.AddDbContext(options => - options.UseSqlite("Data Source=pizza.db")); - - services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) - .AddEntityFrameworkStores(); - - services.AddIdentityServer() - .AddApiAuthorization(); - - services.AddAuthentication() - .AddIdentityServerJwt(); - - services.AddSignalR(options => options.EnableDetailedErrors = true) - .AddMessagePackProtocol(); - - services.AddHostedService(); - services.AddSingleton(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseWebAssemblyDebugging(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseBlazorFrameworkFiles(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseIdentityServer(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapHub("/orderstatus"); - endpoints.MapRazorPages(); - endpoints.MapControllers(); - endpoints.MapFallbackToFile("index.html"); - }); - } - } + app.UseDeveloperExceptionPage(); + app.UseWebAssemblyDebugging(); + } + else + { + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseIdentityServer(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapHub("/orderstatus"); + endpoints.MapRazorPages(); + endpoints.MapControllers(); + endpoints.MapFallbackToFile("index.html"); + }); + } } diff --git a/src/BlazingPizza.Server/ToppingsController.cs b/src/BlazingPizza.Server/ToppingsController.cs index 6a677639..bdcd7caf 100644 --- a/src/BlazingPizza.Server/ToppingsController.cs +++ b/src/BlazingPizza.Server/ToppingsController.cs @@ -1,10 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace BlazingPizza.Server +namespace BlazingPizza.Server { [Route("toppings")] [ApiController] diff --git a/src/BlazingPizza.Shared/Address.cs b/src/BlazingPizza.Shared/Address.cs index d76bc52a..98791224 100644 --- a/src/BlazingPizza.Shared/Address.cs +++ b/src/BlazingPizza.Shared/Address.cs @@ -1,27 +1,26 @@ using System.ComponentModel.DataAnnotations; -namespace BlazingPizza -{ - public class Address - { - public int Id { get; set; } - - [Required, MaxLength(100)] - public string Name { get; set; } - - [Required, MaxLength(100)] - public string Line1 { get; set; } - - [MaxLength(100)] - public string Line2 { get; set; } - - [Required, MaxLength(50)] - public string City { get; set; } - - [Required, MaxLength(20)] - public string Region { get; set; } - - [Required, MaxLength(20)] - public string PostalCode { get; set; } - } +namespace BlazingPizza; + +public class Address +{ + public int Id { get; set; } + + [Required, MaxLength(100)] + public string Name { get; set; } + + [Required, MaxLength(100)] + public string Line1 { get; set; } + + [MaxLength(100)] + public string Line2 { get; set; } + + [Required, MaxLength(50)] + public string City { get; set; } + + [Required, MaxLength(20)] + public string Region { get; set; } + + [Required, MaxLength(20)] + public string PostalCode { get; set; } } diff --git a/src/BlazingPizza.Shared/BlazingPizza.Shared.csproj b/src/BlazingPizza.Shared/BlazingPizza.Shared.csproj index 911c57c5..c11466f8 100644 --- a/src/BlazingPizza.Shared/BlazingPizza.Shared.csproj +++ b/src/BlazingPizza.Shared/BlazingPizza.Shared.csproj @@ -1,7 +1,8 @@  - net5.0 + net6.0 + true BlazingPizza diff --git a/src/BlazingPizza.Shared/LatLong.cs b/src/BlazingPizza.Shared/LatLong.cs index 9b8c3811..459be7f7 100644 --- a/src/BlazingPizza.Shared/LatLong.cs +++ b/src/BlazingPizza.Shared/LatLong.cs @@ -1,27 +1,26 @@ -namespace BlazingPizza -{ - public class LatLong - { - public LatLong() - { - } - - public LatLong(double latitude, double longitude) : this() - { - Latitude = latitude; - Longitude = longitude; - } - - public double Latitude { get; set; } - - public double Longitude { get; set; } - - public static LatLong Interpolate(LatLong start, LatLong end, double proportion) - { - // The Earth is flat, right? So no need for spherical interpolation. - return new LatLong( - start.Latitude + (end.Latitude - start.Latitude) * proportion, - start.Longitude + (end.Longitude - start.Longitude) * proportion); - } - } +namespace BlazingPizza; + +public class LatLong +{ + public LatLong() + { + } + + public LatLong(double latitude, double longitude) : this() + { + Latitude = latitude; + Longitude = longitude; + } + + public double Latitude { get; set; } + + public double Longitude { get; set; } + + public static LatLong Interpolate(LatLong start, LatLong end, double proportion) + { + // The Earth is flat, right? So no need for spherical interpolation. + return new LatLong( + start.Latitude + (end.Latitude - start.Latitude) * proportion, + start.Longitude + (end.Longitude - start.Longitude) * proportion); + } } diff --git a/src/BlazingPizza.Shared/NotificationSubscription.cs b/src/BlazingPizza.Shared/NotificationSubscription.cs index 04259c2f..a0195749 100644 --- a/src/BlazingPizza.Shared/NotificationSubscription.cs +++ b/src/BlazingPizza.Shared/NotificationSubscription.cs @@ -1,17 +1,14 @@ -using System.ComponentModel.DataAnnotations; - -namespace BlazingPizza -{ - public class NotificationSubscription - { - public int NotificationSubscriptionId { get; set; } - - public string UserId { get; set; } - - public string Url { get; set; } - - public string P256dh { get; set; } - - public string Auth { get; set; } - } +namespace BlazingPizza; + +public class NotificationSubscription +{ + public int NotificationSubscriptionId { get; set; } + + public string UserId { get; set; } + + public string Url { get; set; } + + public string P256dh { get; set; } + + public string Auth { get; set; } } diff --git a/src/BlazingPizza.Shared/Order.cs b/src/BlazingPizza.Shared/Order.cs index fb003bd6..09a44398 100644 --- a/src/BlazingPizza.Shared/Order.cs +++ b/src/BlazingPizza.Shared/Order.cs @@ -1,25 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace BlazingPizza -{ - public class Order - { - public int OrderId { get; set; } - - public string UserId { get; set; } - - public DateTime CreatedTime { get; set; } - - public Address DeliveryAddress { get; set; } = new Address(); - - public LatLong DeliveryLocation { get; set; } - - public List Pizzas { get; set; } = new List(); - - public decimal GetTotalPrice() => Pizzas.Sum(p => p.GetTotalPrice()); - - public string GetFormattedTotalPrice() => GetTotalPrice().ToString("0.00"); - } +namespace BlazingPizza; + +public class Order +{ + public int OrderId { get; set; } + + public string UserId { get; set; } + + public DateTime CreatedTime { get; set; } + + public Address DeliveryAddress { get; set; } = new(); + + public LatLong DeliveryLocation { get; set; } + + public List Pizzas { get; set; } = new(); + + public decimal GetTotalPrice() => Pizzas.Sum(p => p.GetTotalPrice()); + + public string GetFormattedTotalPrice() => GetTotalPrice().ToString("0.00"); } diff --git a/src/BlazingPizza.Shared/OrderStatusHubConsts.cs b/src/BlazingPizza.Shared/OrderStatusHubConsts.cs index ea149f65..214593c0 100644 --- a/src/BlazingPizza.Shared/OrderStatusHubConsts.cs +++ b/src/BlazingPizza.Shared/OrderStatusHubConsts.cs @@ -1,16 +1,15 @@ -namespace BlazingPizza +namespace BlazingPizza; + +public class OrderStatusHubConsts { - public class OrderStatusHubConsts + public class MethodNames { - public class MethodNames - { - public const string StartTrackingOrder = nameof(StartTrackingOrder); - public const string StopTrackingOrder = nameof(StopTrackingOrder); - } + public const string StartTrackingOrder = nameof(StartTrackingOrder); + public const string StopTrackingOrder = nameof(StopTrackingOrder); + } - public class EventNames - { - public const string OrderStatusChanged = nameof(OrderStatusChanged); - } + public class EventNames + { + public const string OrderStatusChanged = nameof(OrderStatusChanged); } } diff --git a/src/BlazingPizza.Shared/OrderWithStatus.cs b/src/BlazingPizza.Shared/OrderWithStatus.cs index 021de78c..c01daa2a 100644 --- a/src/BlazingPizza.Shared/OrderWithStatus.cs +++ b/src/BlazingPizza.Shared/OrderWithStatus.cs @@ -1,79 +1,85 @@ using BlazingPizza.ComponentsLibrary.Map; -using System; -using System.Collections.Generic; -namespace BlazingPizza -{ - public class OrderWithStatus - { - public readonly static TimeSpan PreparationDuration = TimeSpan.FromSeconds(10); - public readonly static TimeSpan DeliveryDuration = TimeSpan.FromMinutes(1); // Unrealistic, but more interesting to watch - - public Order Order { get; set; } - - public string StatusText { get; set; } - - public bool IsDelivered => StatusText == "Delivered"; - - public List MapMarkers { get; set; } - - public static OrderWithStatus FromOrder(Order order) - { - // To simulate a real backend process, we fake status updates based on the amount - // of time since the order was placed - string statusText; - List mapMarkers; - var dispatchTime = order.CreatedTime.Add(PreparationDuration); - - if (DateTime.Now < dispatchTime) - { - statusText = "Preparing"; - mapMarkers = new List +namespace BlazingPizza; + +public class OrderWithStatus +{ + public readonly static TimeSpan PreparationDuration = TimeSpan.FromSeconds(10); + public readonly static TimeSpan DeliveryDuration = TimeSpan.FromMinutes(1); // Unrealistic, but more interesting to watch + + public Order Order { get; set; } + + public string StatusText { get; set; } + + public bool IsDelivered => StatusText == "Delivered"; + + public List MapMarkers { get; set; } + + public static OrderWithStatus FromOrder(Order order) + { + // To simulate a real backend process, we fake status updates based on the amount + // of time since the order was placed + string statusText; + List mapMarkers; + var dispatchTime = order.CreatedTime.Add(PreparationDuration); + + if (DateTime.Now < dispatchTime) + { + statusText = "Preparing"; + mapMarkers = new List { ToMapMarker("You", order.DeliveryLocation, showPopup: true) - }; - } - else if (DateTime.Now < dispatchTime + DeliveryDuration) - { - statusText = "Out for delivery"; - - var startPosition = ComputeStartPosition(order); - var proportionOfDeliveryCompleted = Math.Min(1, (DateTime.Now - dispatchTime).TotalMilliseconds / DeliveryDuration.TotalMilliseconds); - var driverPosition = LatLong.Interpolate(startPosition, order.DeliveryLocation, proportionOfDeliveryCompleted); - mapMarkers = new List + }; + } + else if (DateTime.Now < dispatchTime + DeliveryDuration) + { + statusText = "Out for delivery"; + + var startPosition = ComputeStartPosition(order); + var proportionOfDeliveryCompleted = Math.Min(1, (DateTime.Now - dispatchTime).TotalMilliseconds / DeliveryDuration.TotalMilliseconds); + var driverPosition = LatLong.Interpolate(startPosition, order.DeliveryLocation, proportionOfDeliveryCompleted); + mapMarkers = new List { ToMapMarker("You", order.DeliveryLocation), ToMapMarker("Driver", driverPosition, showPopup: true), - }; - } - else - { - statusText = "Delivered"; - mapMarkers = new List + }; + } + else + { + statusText = "Delivered"; + mapMarkers = new List { ToMapMarker("Delivery location", order.DeliveryLocation, showPopup: true), - }; - } - - return new OrderWithStatus - { - Order = order, - StatusText = statusText, - MapMarkers = mapMarkers, - }; - } - - private static LatLong ComputeStartPosition(Order order) - { - // Random but deterministic based on order ID - var rng = new Random(order.OrderId); - var distance = 0.01 + rng.NextDouble() * 0.02; - var angle = rng.NextDouble() * Math.PI * 2; - var offset = (distance * Math.Cos(angle), distance * Math.Sin(angle)); - return new LatLong(order.DeliveryLocation.Latitude + offset.Item1, order.DeliveryLocation.Longitude + offset.Item2); - } - - static Marker ToMapMarker(string description, LatLong coords, bool showPopup = false) - => new Marker { Description = description, X = coords.Longitude, Y = coords.Latitude, ShowPopup = showPopup }; - } + }; + } + + return new OrderWithStatus + { + Order = order, + StatusText = statusText, + MapMarkers = mapMarkers, + }; + } + + private static LatLong ComputeStartPosition(Order order) + { + // Random but deterministic based on order ID + var rng = new Random(order.OrderId); + var distance = 0.01 + rng.NextDouble() * 0.02; + var angle = rng.NextDouble() * Math.PI * 2; + var offset = (distance * Math.Cos(angle), distance * Math.Sin(angle)); + + return new LatLong( + order.DeliveryLocation?.Latitude ?? 0 + offset.Item1, + order.DeliveryLocation?.Longitude ?? 0 + offset.Item2); + } + + static Marker ToMapMarker(string description, LatLong coords, bool showPopup = false) => + new() + { + Description = description, + X = coords?.Longitude ?? 0, + Y = coords?.Latitude ?? 0, + ShowPopup = showPopup + }; } diff --git a/src/BlazingPizza.Shared/Pizza.cs b/src/BlazingPizza.Shared/Pizza.cs index ec1108a9..0023c7bd 100644 --- a/src/BlazingPizza.Shared/Pizza.cs +++ b/src/BlazingPizza.Shared/Pizza.cs @@ -1,42 +1,32 @@ -using System.Collections.Generic; -using System.Linq; - -namespace BlazingPizza -{ - /// - /// Represents a customized pizza as part of an order - /// - public class Pizza - { - public const int DefaultSize = 12; - public const int MinimumSize = 9; - public const int MaximumSize = 17; - - public int Id { get; set; } - - public int OrderId { get; set; } - - public PizzaSpecial Special { get; set; } - - public int SpecialId { get; set; } - - public int Size { get; set; } - - public List Toppings { get; set; } - - public decimal GetBasePrice() - { - return ((decimal)Size / (decimal)DefaultSize) * Special.BasePrice; - } - - public decimal GetTotalPrice() - { - return GetBasePrice() + Toppings.Sum(t => t.Topping.Price); - } - - public string GetFormattedTotalPrice() - { - return GetTotalPrice().ToString("0.00"); - } - } +namespace BlazingPizza; + +/// +/// Represents a customized pizza as part of an order +/// +public class Pizza +{ + public const int DefaultSize = 12; + public const int MinimumSize = 9; + public const int MaximumSize = 17; + + public int Id { get; set; } + + public int OrderId { get; set; } + + public PizzaSpecial Special { get; set; } + + public int SpecialId { get; set; } + + public int Size { get; set; } + + public List Toppings { get; set; } + + public decimal GetBasePrice() => + (decimal)Size / DefaultSize * Special?.BasePrice ?? 1; + + public decimal GetTotalPrice() => + GetBasePrice() + Toppings?.Sum(t => t.Topping.Price) ?? 0; + + public string GetFormattedTotalPrice() => + GetTotalPrice().ToString("0.00"); } diff --git a/src/BlazingPizza.Shared/PizzaSpecial.cs b/src/BlazingPizza.Shared/PizzaSpecial.cs index 2f2b3383..4542b26d 100644 --- a/src/BlazingPizza.Shared/PizzaSpecial.cs +++ b/src/BlazingPizza.Shared/PizzaSpecial.cs @@ -1,20 +1,19 @@ -namespace BlazingPizza -{ - /// - /// Represents a pre-configured template for a pizza a user can order - /// - public class PizzaSpecial - { - public int Id { get; set; } - - public string Name { get; set; } - - public decimal BasePrice { get; set; } - - public string Description { get; set; } - - public string ImageUrl { get; set; } - - public string GetFormattedBasePrice() => BasePrice.ToString("0.00"); - } +namespace BlazingPizza; + +/// +/// Represents a pre-configured template for a pizza a user can order +/// +public class PizzaSpecial +{ + public int Id { get; set; } + + public string Name { get; set; } + + public decimal BasePrice { get; set; } + + public string Description { get; set; } + + public string ImageUrl { get; set; } + + public string GetFormattedBasePrice() => BasePrice.ToString("0.00"); } diff --git a/src/BlazingPizza.Shared/PizzaTopping.cs b/src/BlazingPizza.Shared/PizzaTopping.cs index f5a6c1ac..21a0a90a 100644 --- a/src/BlazingPizza.Shared/PizzaTopping.cs +++ b/src/BlazingPizza.Shared/PizzaTopping.cs @@ -1,11 +1,10 @@ -namespace BlazingPizza -{ - public class PizzaTopping - { - public Topping Topping { get; set; } - - public int ToppingId { get; set; } - - public int PizzaId { get; set; } - } +namespace BlazingPizza; + +public class PizzaTopping +{ + public Topping Topping { get; set; } + + public int ToppingId { get; set; } + + public int PizzaId { get; set; } } diff --git a/src/BlazingPizza.Shared/Topping.cs b/src/BlazingPizza.Shared/Topping.cs index 61bdeb92..de2d7b7a 100644 --- a/src/BlazingPizza.Shared/Topping.cs +++ b/src/BlazingPizza.Shared/Topping.cs @@ -1,13 +1,12 @@ -namespace BlazingPizza -{ - public class Topping - { - public int Id { get; set; } - - public string Name { get; set; } - - public decimal Price { get; set; } - - public string GetFormattedPrice() => Price.ToString("0.00"); - } +namespace BlazingPizza; + +public class Topping +{ + public int Id { get; set; } + + public string Name { get; set; } + + public decimal Price { get; set; } + + public string GetFormattedPrice() => Price.ToString("0.00"); } diff --git a/src/BlazingPizza.Shared/UserInfo.cs b/src/BlazingPizza.Shared/UserInfo.cs index 4996ef80..299c6db6 100644 --- a/src/BlazingPizza.Shared/UserInfo.cs +++ b/src/BlazingPizza.Shared/UserInfo.cs @@ -1,9 +1,8 @@ -namespace BlazingPizza -{ - public class UserInfo - { - public bool IsAuthenticated { get; set; } - - public string Name { get; set; } - } +namespace BlazingPizza; + +public class UserInfo +{ + public bool IsAuthenticated { get; set; } + + public string Name { get; set; } } diff --git a/src/BlazingPizza.sln b/src/BlazingPizza.sln index 1db6afe7..65d4da16 100644 --- a/src/BlazingPizza.sln +++ b/src/BlazingPizza.sln @@ -1,92 +1,103 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28902.138 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.Server", "BlazingPizza.Server\BlazingPizza.Server.csproj", "{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.Client", "BlazingPizza.Client\BlazingPizza.Client.csproj", "{C33040F6-6F46-4AA2-8A8D-A97C934A0856}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.Shared", "BlazingPizza.Shared\BlazingPizza.Shared.csproj", "{42410116-06C1-4D19-B5FC-BD1F85F918B3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.ComponentsLibrary", "BlazingPizza.ComponentsLibrary\BlazingPizza.ComponentsLibrary.csproj", "{0C421DC9-9F9A-4C97-9BC5-D959E8840864}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazingComponents", "BlazingComponents\BlazingComponents.csproj", "{4C4132E7-D096-42DA-9C3D-C444B42C3889}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x64.ActiveCfg = Debug|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x64.Build.0 = Debug|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x86.ActiveCfg = Debug|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x86.Build.0 = Debug|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|Any CPU.Build.0 = Release|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x64.ActiveCfg = Release|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x64.Build.0 = Release|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x86.ActiveCfg = Release|Any CPU - {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x86.Build.0 = Release|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x64.ActiveCfg = Debug|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x64.Build.0 = Debug|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x86.ActiveCfg = Debug|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x86.Build.0 = Debug|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|Any CPU.Build.0 = Release|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x64.ActiveCfg = Release|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x64.Build.0 = Release|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x86.ActiveCfg = Release|Any CPU - {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x86.Build.0 = Release|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x64.ActiveCfg = Debug|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x64.Build.0 = Debug|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x86.ActiveCfg = Debug|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x86.Build.0 = Debug|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|Any CPU.Build.0 = Release|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x64.ActiveCfg = Release|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x64.Build.0 = Release|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x86.ActiveCfg = Release|Any CPU - {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x86.Build.0 = Release|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x64.ActiveCfg = Debug|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x64.Build.0 = Debug|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x86.ActiveCfg = Debug|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x86.Build.0 = Debug|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|Any CPU.Build.0 = Release|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x64.ActiveCfg = Release|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x64.Build.0 = Release|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x86.ActiveCfg = Release|Any CPU - {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x86.Build.0 = Release|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x64.ActiveCfg = Debug|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x64.Build.0 = Debug|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x86.ActiveCfg = Debug|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x86.Build.0 = Debug|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|Any CPU.Build.0 = Release|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x64.ActiveCfg = Release|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x64.Build.0 = Release|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x86.ActiveCfg = Release|Any CPU - {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9574C5F7-86D1-4384-85AC-EA7D09B201AD} - EndGlobalSection -EndGlobal +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.Server", "BlazingPizza.Server\BlazingPizza.Server.csproj", "{29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.Client", "BlazingPizza.Client\BlazingPizza.Client.csproj", "{C33040F6-6F46-4AA2-8A8D-A97C934A0856}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.Shared", "BlazingPizza.Shared\BlazingPizza.Shared.csproj", "{42410116-06C1-4D19-B5FC-BD1F85F918B3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingPizza.ComponentsLibrary", "BlazingPizza.ComponentsLibrary\BlazingPizza.ComponentsLibrary.csproj", "{0C421DC9-9F9A-4C97-9BC5-D959E8840864}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazingComponents", "BlazingComponents\BlazingComponents.csproj", "{4C4132E7-D096-42DA-9C3D-C444B42C3889}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{22A97BCA-BB12-446D-B2BB-767688A86D1E}" + ProjectSection(SolutionItems) = preProject + ..\.editorconfig = ..\.editorconfig + ..\.gitignore = ..\.gitignore + ..\Directory.Build.props = ..\Directory.Build.props + ..\LICENSE = ..\LICENSE + nuget.config = nuget.config + ..\README.md = ..\README.md + ..\THIRD-PARTY-NOTICES.md = ..\THIRD-PARTY-NOTICES.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x64.Build.0 = Debug|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x86.ActiveCfg = Debug|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Debug|x86.Build.0 = Debug|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|Any CPU.Build.0 = Release|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x64.ActiveCfg = Release|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x64.Build.0 = Release|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x86.ActiveCfg = Release|Any CPU + {29F85A53-A43B-4B5F-8C11-4FC62CDF19D6}.Release|x86.Build.0 = Release|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x64.ActiveCfg = Debug|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x64.Build.0 = Debug|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x86.ActiveCfg = Debug|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Debug|x86.Build.0 = Debug|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|Any CPU.Build.0 = Release|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x64.ActiveCfg = Release|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x64.Build.0 = Release|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x86.ActiveCfg = Release|Any CPU + {C33040F6-6F46-4AA2-8A8D-A97C934A0856}.Release|x86.Build.0 = Release|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x64.ActiveCfg = Debug|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x64.Build.0 = Debug|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x86.ActiveCfg = Debug|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Debug|x86.Build.0 = Debug|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|Any CPU.Build.0 = Release|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x64.ActiveCfg = Release|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x64.Build.0 = Release|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x86.ActiveCfg = Release|Any CPU + {42410116-06C1-4D19-B5FC-BD1F85F918B3}.Release|x86.Build.0 = Release|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x64.Build.0 = Debug|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Debug|x86.Build.0 = Debug|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|Any CPU.Build.0 = Release|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x64.ActiveCfg = Release|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x64.Build.0 = Release|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x86.ActiveCfg = Release|Any CPU + {0C421DC9-9F9A-4C97-9BC5-D959E8840864}.Release|x86.Build.0 = Release|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x64.ActiveCfg = Debug|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x64.Build.0 = Debug|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x86.ActiveCfg = Debug|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Debug|x86.Build.0 = Debug|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|Any CPU.Build.0 = Release|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x64.ActiveCfg = Release|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x64.Build.0 = Release|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x86.ActiveCfg = Release|Any CPU + {4C4132E7-D096-42DA-9C3D-C444B42C3889}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9574C5F7-86D1-4384-85AC-EA7D09B201AD} + EndGlobalSection +EndGlobal From 000a3844d20ed5f34cc899738c0783358a7e5241 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 15 Nov 2021 11:31:12 -0600 Subject: [PATCH 6/6] Fix the spacing of the .props --- Directory.Build.props | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index ce304b82..b618152d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,9 +1,9 @@ - - 6.0.0 - 6.0.0 - 6.0.0 - 6.0.0 - 6.0.0 - + + 6.0.0 + 6.0.0 + 6.0.0 + 6.0.0 + 6.0.0 +