diff --git a/.gitignore b/.gitignore index af2c4680..1e8d5846 100644 --- a/.gitignore +++ b/.gitignore @@ -337,3 +337,6 @@ ASALocalRun/ # Visual Studio Code folder .vscode/ +*.db-shm +*.db-wal +/save-points/08-templated-components/BlazingPizza.Client/BlazingPizza.Client.sln diff --git a/docs/00-get-started.md b/docs/00-get-started.md index 40a4616c..20e6b57c 100644 --- a/docs/00-get-started.md +++ b/docs/00-get-started.md @@ -4,7 +4,7 @@ In this session, you'll setup your machine for Blazor development and build your ## Setup -To get started with Blazor, follow the getting instructions on [blazor.net](https://blazor.net). +To get started with Blazor, follow the getting instructions on [blazor.net](https://dotnet.microsoft.com/en-us/learn/aspnet/blazor-tutorial/intro). ## Build your first app diff --git a/docs/01-components-and-layout.md b/docs/01-components-and-layout.md index d6f8843a..107e54b4 100644 --- a/docs/01-components-and-layout.md +++ b/docs/01-components-and-layout.md @@ -42,7 +42,7 @@ Add a `@code` block to *Index.razor* with a list field to keep track of the avai ```csharp @code { - List specials; + List? specials; } ``` @@ -61,7 +61,7 @@ Override the `OnInitializedAsync` method in the `@code` block to retrieve the li ```csharp @code { - List specials; + List? specials; protected override async Task OnInitializedAsync() { @@ -81,7 +81,7 @@ Once the component is initialized it will render its markup. Replace the markup ```html
    - @if (specials != null) + @if (specials is not null) { @foreach (var special in specials) { diff --git a/docs/02-customize-a-pizza.md b/docs/02-customize-a-pizza.md index dc3f88d4..d2e69c9c 100644 --- a/docs/02-customize-a-pizza.md +++ b/docs/02-customize-a-pizza.md @@ -31,8 +31,8 @@ The `@` symbol is used in Razor files to indicate the start of C# code. Surround Update the `@code` block in *Index.razor* to add some additional fields for tracking the pizza being customized and whether the pizza customization dialog is visible. ```csharp -List specials; -Pizza configuringPizza; +List? specials; +Pizza? configuringPizza; bool showingConfigureDialog; ``` @@ -67,11 +67,13 @@ Add a *ConfigurePizzaDialog.razor* file under the *Shared* directory. Since this > Note: In Visual Studio, you can right-click the *Shared* directory in Solution Explorer, then choose *Add* -> *New Item* to use the *Razor Component* item template to add a new Razor component. -The `ConfigurePizzaDialog` should have a `Pizza` parameter that specifies the pizza being configured. Component parameters are defined by adding a writable property to the component decorated with the `[Parameter]` attribute. Add a `@code` block to the `ConfigurePizzaDialog` with the following `Pizza` parameter: +The `ConfigurePizzaDialog` should have a `Pizza` parameter that specifies the pizza being configured. Component parameters are defined by adding a writable property to the component decorated with the `[Parameter]` attribute. Because the `Pizza` parameter requires a value for the component to function, the `[EditorRequired]` attribute is also added. By adding the `[EditorRequired]` attribute, if a parameter value isn't provided, editors or build tools may display warnings to the user. + +Add a `@code` block to the `ConfigurePizzaDialog` with the following `Pizza` parameter: ```csharp @code { - [Parameter] public Pizza Pizza { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); } ``` @@ -80,22 +82,22 @@ The `ConfigurePizzaDialog` should have a `Pizza` parameter that specifies the pi Add the following basic markup for the `ConfigurePizzaDialog`: ```html -
    -
    -
    -

    @Pizza.Special.Name

    - @Pizza.Special.Description -
    -
    -
    - - - Price: @(Pizza.GetFormattedTotalPrice()) - - +
    +
    +
    +

    @Pizza.Special?.Name

    + @Pizza.Special?.Description +
    +
    +
    + + + Price: @(Pizza.GetFormattedTotalPrice()) + + +
    -
    ``` Update *Pages/Index.razor* to show the `ConfigurePizzaDialog` when a pizza special has been selected. The `ConfigurePizzaDialog` is styled to overlay the current page, so it doesn't really matter where you put this code block. @@ -145,7 +147,7 @@ If you wanted to implement two-way binding manually, you could do so by combinin max="@Pizza.MaximumSize" step="1" value="@Pizza.Size" - @onchange="@((ChangeEventArgs e) => Pizza.Size = int.Parse((string) e.Value))" /> + @onchange="@((ChangeEventArgs e) => Pizza.Size = int.Parse((string?) e.Value))" /> ``` In Blazor you can use the `@bind` directive attribute to specify a two-way binding with this same behavior. The equivalent markup using `@bind` looks like this: @@ -180,13 +182,14 @@ The user should also be able to select additional toppings on `ConfigurePizzaDia
    @code { - List toppings; + // toppings is only null while loading + List toppings = null!; - [Parameter] public Pizza Pizza { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = default!; protected async override Task OnInitializedAsync() { - toppings = await HttpClient.GetFromJsonAsync>("toppings"); + toppings = await HttpClient.GetFromJsonAsync>("toppings") ?? new(); } } ``` @@ -196,7 +199,7 @@ Add the following markup in the dialog body for displaying a drop down list with ```html
    - @if (toppings == null) + @if (toppings is null) { @@ -41,11 +41,14 @@
    @foreach (var topping in Pizza.Toppings) { -
    - @topping.Topping.Name - @topping.Topping.GetFormattedPrice() - -
    + if (topping?.Topping is not null) + { +
    + @topping.Topping.Name + @topping.Topping.GetFormattedPrice() + +
    + } }
    @@ -61,20 +64,21 @@
    @code { - List toppings; + List? toppings; - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnCancel { get; set; } - [Parameter] public EventCallback OnConfirm { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnCancel { get; set; } + [Parameter, EditorRequired] public EventCallback OnConfirm { get; set; } protected async override Task OnInitializedAsync() { - toppings = await HttpClient.GetFromJsonAsync>("toppings"); + toppings = await HttpClient.GetFromJsonAsync>("toppings") ?? new(); } void ToppingSelected(ChangeEventArgs e) { - if (int.TryParse((string)e.Value, out var index) && index >= 0) + if (toppings is null) return; + if (int.TryParse((string?)e.Value, out var index) && index >= 0) { AddTopping(toppings[index]); } @@ -82,7 +86,7 @@ void AddTopping(Topping topping) { - if (Pizza.Toppings.Find(pt => pt.Topping == topping) == null) + if (Pizza.Toppings.Find(pt => pt.Topping == topping) is null) { Pizza.Toppings.Add(new PizzaTopping() { Topping = topping }); } diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor b/save-points/02-customize-a-pizza/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor index e0e9f41d..f61d8a63 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor +++ b/save-points/02-customize-a-pizza/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor @@ -1,10 +1,10 @@ 
    x -
    @(Pizza.Size)" @Pizza.Special.Name
    +
    @(Pizza.Size)" @Pizza.Special?.Name
      @foreach (var topping in Pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    @@ -13,6 +13,6 @@
    @code { - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnRemoved { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnRemoved { get; set; } } \ No newline at end of file diff --git a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj index cb996b1b..a907e880 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj +++ b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj @@ -2,7 +2,8 @@ $(TargetFrameworkVersion) - true + true + enable diff --git a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/LocalStorage.cs b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/LocalStorage.cs index 8995307f..be8226ec 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/LocalStorage.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/LocalStorage.cs @@ -4,7 +4,6 @@ namespace BlazingPizza.ComponentsLibrary; public static class LocalStorage { - public static ValueTask GetAsync(IJSRuntime jsRuntime, string key) => jsRuntime.InvokeAsync("blazorLocalStorage.get", key); @@ -13,5 +12,4 @@ public static ValueTask SetAsync(IJSRuntime jsRuntime, string key, object value) public static ValueTask DeleteAsync(IJSRuntime jsRuntime, string key) => jsRuntime.InvokeVoidAsync("blazorLocalStorage.delete", key); - } \ No newline at end of file diff --git a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Map.razor b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Map.razor index c2413ac4..4325755e 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Map.razor +++ b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Map.razor @@ -5,9 +5,9 @@ @code { string elementId = $"map-{Guid.NewGuid().ToString("D")}"; - + [Parameter] public double Zoom { get; set; } - [Parameter] public List Markers { get; set; } + [Parameter, EditorRequired] public List Markers { get; set; } = new(); protected async override Task OnAfterRenderAsync(bool firstRender) { diff --git a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Marker.cs b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Marker.cs index 63006067..d4dcdbad 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Marker.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Marker.cs @@ -1,13 +1,12 @@ -namespace BlazingPizza.ComponentsLibrary.Map +namespace BlazingPizza.ComponentsLibrary.Map; + +public class Marker { - public class Marker - { - public string Description { get; set; } + public string Description { get; set; } = string.Empty; - public double X { get; set; } + public double X { get; set; } - public double Y { get; set; } + public double Y { get; set; } - public bool ShowPopup { get; set; } - } -} + public bool ShowPopup { get; set; } +} \ No newline at end of file diff --git a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Point.cs b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Point.cs index 03841826..054e56ec 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Point.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.ComponentsLibrary/Map/Point.cs @@ -1,9 +1,9 @@ -namespace BlazingPizza.ComponentsLibrary.Map +namespace BlazingPizza.ComponentsLibrary.Map; + +public class Point { - public class Point - { - public double X { get; set; } + public double X { get; set; } - public double Y { get; set; } - } + public double Y { get; set; } } +// Compare this snippet from save-points\01-Components-and-layout\BlazingPizza.ComponentsLibrary\Map\Marker.cs: \ No newline at end of file diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/save-points/02-customize-a-pizza/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml index a1b61965..3f740c74 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml +++ b/save-points/02-customize-a-pizza/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -17,7 +17,7 @@ {
    - @User.Identity.Name + @User?.Identity?.Name
    diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Server/BlazingPizza.Server.csproj b/save-points/02-customize-a-pizza/BlazingPizza.Server/BlazingPizza.Server.csproj index 227890e1..a1043cb6 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Server/BlazingPizza.Server.csproj +++ b/save-points/02-customize-a-pizza/BlazingPizza.Server/BlazingPizza.Server.csproj @@ -3,6 +3,7 @@ $(TargetFrameworkVersion) true + enable diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Server/OrdersController.cs b/save-points/02-customize-a-pizza/BlazingPizza.Server/OrdersController.cs index 37e37cc5..9e78e746 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Server/OrdersController.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Server/OrdersController.cs @@ -20,7 +20,7 @@ public OrdersController(PizzaStoreContext db) public async Task>> GetOrders() { var orders = await _db.Orders - // .Where(o => o.UserId == GetUserId()) + // .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -35,7 +35,7 @@ public async Task> GetOrderWithStatus(int orderId) { var order = await _db.Orders .Where(o => o.OrderId == orderId) - // .Where(o => o.UserId == GetUserId()) + // .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -54,19 +54,19 @@ public async Task> PlaceOrder(Order order) { order.CreatedTime = DateTime.Now; order.DeliveryLocation = new LatLong(51.5001, -0.1239); - // order.UserId = GetUserId(); + // order.UserId = PizzaApiExtensions.GetUserId(HttpContext); // 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.SpecialId = pizza.Special?.Id ?? 0; pizza.Special = null; foreach (var topping in pizza.Toppings) { - topping.ToppingId = topping.Topping.Id; + topping.ToppingId = topping.Topping?.Id ?? 0; topping.Topping = null; } } @@ -75,7 +75,7 @@ public async Task> PlaceOrder(Order order) await _db.SaveChangesAsync(); // In the background, send push notifications if possible - var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); + var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == PizzaApiExtensions.GetUserId(HttpContext)).SingleOrDefaultAsync(); if (subscription != null) { _ = TrackAndSendNotificationsAsync(order, subscription); @@ -84,11 +84,6 @@ public async Task> PlaceOrder(Order order) return order.OrderId; } - private string GetUserId() - { - return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - } - private static async Task TrackAndSendNotificationsAsync(Order order, NotificationSubscription subscription) { // In a realistic case, some other backend process would track diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Server/PizzaApiExtensions.cs b/save-points/02-customize-a-pizza/BlazingPizza.Server/PizzaApiExtensions.cs index 524ca759..d277597f 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Server/PizzaApiExtensions.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Server/PizzaApiExtensions.cs @@ -19,6 +19,10 @@ public static WebApplication MapPizzaApi(this WebApplication app) // 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(context); + if (userId is null) + { + return Results.Unauthorized(); + } var oldSubscriptions = db.NotificationSubscriptions.Where(e => e.UserId == userId); db.NotificationSubscriptions.RemoveRange(oldSubscriptions); @@ -49,9 +53,6 @@ public static WebApplication MapPizzaApi(this WebApplication app) } - private static string GetUserId(HttpContext context) - { - return context.User.FindFirstValue(ClaimTypes.NameIdentifier); - } + public static string? GetUserId(HttpContext context) => context.User.FindFirstValue(ClaimTypes.NameIdentifier); } \ No newline at end of file diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Server/PizzaStoreContext.cs b/save-points/02-customize-a-pizza/BlazingPizza.Server/PizzaStoreContext.cs index 50ac87e4..1505c7af 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Server/PizzaStoreContext.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Server/PizzaStoreContext.cs @@ -35,4 +35,4 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // Inline the Lat-Long pairs in Order rather than having a FK to another table modelBuilder.Entity().OwnsOne(o => o.DeliveryLocation); } -} +} \ No newline at end of file diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Server/Program.cs b/save-points/02-customize-a-pizza/BlazingPizza.Server/Program.cs index b83debb4..e185db50 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Server/Program.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Server/Program.cs @@ -11,8 +11,7 @@ builder.Services.AddRazorPages(); builder.Services.AddDbContext(options => - options.UseSqlite("Data Source=pizza.db") - .UseModel(BlazingPizza.Server.Models.PizzaStoreContextModel.Instance)); + options.UseSqlite("Data Source=pizza.db")); builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores(); diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/Address.cs b/save-points/02-customize-a-pizza/BlazingPizza.Shared/Address.cs index f2d1daa4..d1a53375 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/Address.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/Address.cs @@ -1,20 +1,18 @@ -using System.ComponentModel.DataAnnotations; - -namespace BlazingPizza; +namespace BlazingPizza; public class Address { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; - public string Line1 { get; set; } + public string Line1 { get; set; } = string.Empty; - public string Line2 { get; set; } + public string Line2 { get; set; } = string.Empty; - public string City { get; set; } + public string City { get; set; } = string.Empty; - public string Region { get; set; } + public string Region { get; set; } = string.Empty; - public string PostalCode { get; set; } + public string PostalCode { get; set; } = string.Empty; } diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/BlazingPizza.Shared.csproj b/save-points/02-customize-a-pizza/BlazingPizza.Shared/BlazingPizza.Shared.csproj index 075ddd60..785c204e 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/BlazingPizza.Shared.csproj +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/BlazingPizza.Shared.csproj @@ -1,13 +1,18 @@  - - $(TargetFrameworkVersion) - BlazingPizza + + $(TargetFrameworkVersion) + BlazingPizza true - + enable + - - - + + + + + + + diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/NotificationSubscription.cs b/save-points/02-customize-a-pizza/BlazingPizza.Shared/NotificationSubscription.cs index 5c99e3f4..7f1ea4d9 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/NotificationSubscription.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/NotificationSubscription.cs @@ -2,13 +2,13 @@ public class NotificationSubscription { - public int NotificationSubscriptionId { get; set; } + public int? NotificationSubscriptionId { get; set; } - public string UserId { get; set; } + public string? UserId { get; set; } - public string Url { get; set; } + public string? Url { get; set; } - public string P256dh { get; set; } + public string? P256dh { get; set; } - public string Auth { get; set; } -} + public string? Auth { get; set; } +} \ No newline at end of file diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/Order.cs b/save-points/02-customize-a-pizza/BlazingPizza.Shared/Order.cs index 180ec55b..f1211588 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/Order.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/Order.cs @@ -6,13 +6,15 @@ public class Order { public int OrderId { get; set; } - public string UserId { get; set; } + // Set by the server during POST + public string? UserId { get; set; } public DateTime CreatedTime { get; set; } public Address DeliveryAddress { get; set; } = new Address(); - public LatLong DeliveryLocation { get; set; } + // Set by server during POST + public LatLong? DeliveryLocation { get; set; } public List Pizzas { get; set; } = new List(); diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/OrderWithStatus.cs b/save-points/02-customize-a-pizza/BlazingPizza.Shared/OrderWithStatus.cs index fbfac7af..c0e3495a 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/OrderWithStatus.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/OrderWithStatus.cs @@ -9,16 +9,19 @@ 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; } + // Set from DB + public Order Order { get; set; } = null!; - public string StatusText { get; set; } + // Set from Order + public string StatusText { get; set; } = null!; public bool IsDelivered => StatusText == "Delivered"; - public List MapMarkers { get; set; } + public List MapMarkers { get; set; } = null!; public static OrderWithStatus FromOrder(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // To simulate a real backend process, we fake status updates based on the amount // of time since the order was placed string statusText; @@ -65,6 +68,7 @@ public static OrderWithStatus FromOrder(Order order) private static LatLong ComputeStartPosition(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // Random but deterministic based on order ID var rng = new Random(order.OrderId); var distance = 0.01 + rng.NextDouble() * 0.02; diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/Pizza.cs b/save-points/02-customize-a-pizza/BlazingPizza.Shared/Pizza.cs index 2a7a1c29..150de954 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/Pizza.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/Pizza.cs @@ -15,22 +15,24 @@ public class Pizza public int OrderId { get; set; } - public PizzaSpecial Special { get; set; } + public PizzaSpecial? Special { get; set; } public int SpecialId { get; set; } public int Size { get; set; } - public List Toppings { get; set; } + public List Toppings { get; set; } = new(); public decimal GetBasePrice() { + if(Special == null) throw new NullReferenceException($"{nameof(Special)} was null when calculating Base Price."); return ((decimal)Size / (decimal)DefaultSize) * Special.BasePrice; } public decimal GetTotalPrice() { - return GetBasePrice() + Toppings.Sum(t => t.Topping.Price); + if (Toppings.Any(t => t.Topping is null)) throw new NullReferenceException($"{nameof(Toppings)} contained null when calculating the Total Price."); + return GetBasePrice() + Toppings.Sum(t => t.Topping!.Price); } public string GetFormattedTotalPrice() diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/PizzaSpecial.cs b/save-points/02-customize-a-pizza/BlazingPizza.Shared/PizzaSpecial.cs index 1fd9006a..d53ab286 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/PizzaSpecial.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/PizzaSpecial.cs @@ -7,13 +7,13 @@ public class PizzaSpecial { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal BasePrice { get; set; } - public string Description { get; set; } + public string Description { get; set; } = string.Empty; - public string ImageUrl { get; set; } + public string ImageUrl { get; set; } = "img/pizzas/cheese.jpg"; public string GetFormattedBasePrice() => BasePrice.ToString("0.00"); } \ No newline at end of file diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/PizzaTopping.cs b/save-points/02-customize-a-pizza/BlazingPizza.Shared/PizzaTopping.cs index 1c062732..724f1c06 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/PizzaTopping.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/PizzaTopping.cs @@ -2,7 +2,7 @@ public class PizzaTopping { - public Topping Topping { get; set; } + public Topping? Topping { get; set; } public int ToppingId { get; set; } diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/Topping.cs b/save-points/02-customize-a-pizza/BlazingPizza.Shared/Topping.cs index 4eb67ae9..29b9e790 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/Topping.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/Topping.cs @@ -4,7 +4,7 @@ public class Topping { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal Price { get; set; } diff --git a/save-points/02-customize-a-pizza/BlazingPizza.Shared/UserInfo.cs b/save-points/02-customize-a-pizza/BlazingPizza.Shared/UserInfo.cs index babb4315..fb0045bf 100644 --- a/save-points/02-customize-a-pizza/BlazingPizza.Shared/UserInfo.cs +++ b/save-points/02-customize-a-pizza/BlazingPizza.Shared/UserInfo.cs @@ -4,6 +4,6 @@ public class UserInfo { public bool IsAuthenticated { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; } \ No newline at end of file diff --git a/save-points/03-show-order-status/BlazingPizza.Client/BlazingPizza.Client.csproj b/save-points/03-show-order-status/BlazingPizza.Client/BlazingPizza.Client.csproj index f9ebe283..48b9455e 100644 --- a/save-points/03-show-order-status/BlazingPizza.Client/BlazingPizza.Client.csproj +++ b/save-points/03-show-order-status/BlazingPizza.Client/BlazingPizza.Client.csproj @@ -1,20 +1,21 @@  - - $(TargetFrameworkVersion) + + $(TargetFrameworkVersion) true - + enable + - - - - - - + + + + + + - - - - + + + + diff --git a/save-points/03-show-order-status/BlazingPizza.Client/Pages/Index.razor b/save-points/03-show-order-status/BlazingPizza.Client/Pages/Index.razor index 1914e003..3d2588fc 100644 --- a/save-points/03-show-order-status/BlazingPizza.Client/Pages/Index.razor +++ b/save-points/03-show-order-status/BlazingPizza.Client/Pages/Index.razor @@ -55,8 +55,8 @@ } @code { - List specials; - Pizza configuringPizza; + List? specials; + Pizza? configuringPizza; bool showingConfigureDialog; Order order = new Order(); @@ -86,8 +86,11 @@ void ConfirmConfigurePizzaDialog() { - order.Pizzas.Add(configuringPizza); - configuringPizza = null; + if (configuringPizza is not null) + { + order.Pizzas.Add(configuringPizza); + configuringPizza = null; + } showingConfigureDialog = false; } diff --git a/save-points/03-show-order-status/BlazingPizza.Client/Pages/MyOrders.razor b/save-points/03-show-order-status/BlazingPizza.Client/Pages/MyOrders.razor index 36d3902e..3c5aefdd 100644 --- a/save-points/03-show-order-status/BlazingPizza.Client/Pages/MyOrders.razor +++ b/save-points/03-show-order-status/BlazingPizza.Client/Pages/MyOrders.razor @@ -4,7 +4,7 @@ Blazing Pizza - My Orders
    - @if (ordersWithStatus == null) + @if (ordersWithStatus is null) { Loading... } @@ -41,7 +41,7 @@
    @code { - IEnumerable ordersWithStatus; + IEnumerable? ordersWithStatus; protected override async Task OnParametersSetAsync() { diff --git a/save-points/03-show-order-status/BlazingPizza.Client/Pages/OrderDetails.razor b/save-points/03-show-order-status/BlazingPizza.Client/Pages/OrderDetails.razor index 25e06141..ced45b7c 100644 --- a/save-points/03-show-order-status/BlazingPizza.Client/Pages/OrderDetails.razor +++ b/save-points/03-show-order-status/BlazingPizza.Client/Pages/OrderDetails.razor @@ -11,7 +11,7 @@

    Nope

    Sorry, this order could not be loaded.

    } - else if (orderWithStatus == null) + else if (orderWithStatus is null) { Loading... } @@ -38,9 +38,9 @@ @code { [Parameter] public int OrderId { get; set; } - OrderWithStatus orderWithStatus; + OrderWithStatus? orderWithStatus; bool invalidOrder; - CancellationTokenSource pollingCancellationToken; + CancellationTokenSource? pollingCancellationToken; protected override void OnParametersSet() { @@ -59,7 +59,7 @@ try { invalidOrder = false; - orderWithStatus = await HttpClient.GetFromJsonAsync($"orders/{OrderId}"); + orderWithStatus = await HttpClient.GetFromJsonAsync($"orders/{OrderId}") ?? throw new NullReferenceException(); StateHasChanged(); if (orderWithStatus.IsDelivered) diff --git a/save-points/03-show-order-status/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor b/save-points/03-show-order-status/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor index 56549454..e643d7b3 100644 --- a/save-points/03-show-order-status/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor +++ b/save-points/03-show-order-status/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor @@ -3,8 +3,8 @@
    -

    @Pizza.Special.Name

    - @Pizza.Special.Description +

    @Pizza.Special?.Name

    + @Pizza.Special?.Description
    @@ -16,7 +16,7 @@
    - @if (toppings == null) + @if (toppings is null) { @@ -41,11 +41,14 @@
    @foreach (var topping in Pizza.Toppings) { -
    - @topping.Topping.Name - @topping.Topping.GetFormattedPrice() - -
    + if (topping?.Topping is not null) + { +
    + @topping.Topping.Name + @topping.Topping.GetFormattedPrice() + +
    + } }
    @@ -61,20 +64,21 @@
    @code { - List toppings; + List? toppings; - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnCancel { get; set; } - [Parameter] public EventCallback OnConfirm { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnCancel { get; set; } + [Parameter, EditorRequired] public EventCallback OnConfirm { get; set; } protected async override Task OnInitializedAsync() { - toppings = await HttpClient.GetFromJsonAsync>("toppings"); + toppings = await HttpClient.GetFromJsonAsync>("toppings") ?? new(); } void ToppingSelected(ChangeEventArgs e) { - if (int.TryParse((string)e.Value, out var index) && index >= 0) + if (toppings is null) return; + if (int.TryParse((string?)e.Value, out var index) && index >= 0) { AddTopping(toppings[index]); } @@ -82,7 +86,7 @@ void AddTopping(Topping topping) { - if (Pizza.Toppings.Find(pt => pt.Topping == topping) == null) + if (Pizza.Toppings.Find(pt => pt.Topping == topping) is null) { Pizza.Toppings.Add(new PizzaTopping() { Topping = topping }); } diff --git a/save-points/04-refactor-state-management/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor b/save-points/04-refactor-state-management/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor index e0e9f41d..f7f7a31d 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor +++ b/save-points/04-refactor-state-management/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor @@ -1,10 +1,10 @@ 
    x -
    @(Pizza.Size)" @Pizza.Special.Name
    +
    @(Pizza.Size)" @Pizza.Special?.Name
      @foreach (var topping in Pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    @@ -13,6 +13,6 @@
    @code { - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnRemoved { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnRemoved { get; set; } } \ No newline at end of file diff --git a/save-points/04-refactor-state-management/BlazingPizza.Client/Shared/OrderReview.razor b/save-points/04-refactor-state-management/BlazingPizza.Client/Shared/OrderReview.razor index e1424934..56668721 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Client/Shared/OrderReview.razor +++ b/save-points/04-refactor-state-management/BlazingPizza.Client/Shared/OrderReview.razor @@ -3,7 +3,7 @@

    @(pizza.Size)" - @pizza.Special.Name + @pizza.Special?.Name (£@pizza.GetFormattedTotalPrice())

    @@ -11,7 +11,7 @@
      @foreach (var topping in pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    } @@ -24,5 +24,5 @@

    @code { - [Parameter] public Order Order { get; set; } + [Parameter, EditorRequired] public Order Order { get; set; } = new(); } diff --git a/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj b/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj index cb996b1b..a907e880 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj +++ b/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj @@ -2,7 +2,8 @@ $(TargetFrameworkVersion) - true + true + enable diff --git a/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Map.razor b/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Map.razor index c2413ac4..4325755e 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Map.razor +++ b/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Map.razor @@ -5,9 +5,9 @@ @code { string elementId = $"map-{Guid.NewGuid().ToString("D")}"; - + [Parameter] public double Zoom { get; set; } - [Parameter] public List Markers { get; set; } + [Parameter, EditorRequired] public List Markers { get; set; } = new(); protected async override Task OnAfterRenderAsync(bool firstRender) { diff --git a/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Marker.cs b/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Marker.cs index b067719c..d4dcdbad 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Marker.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Marker.cs @@ -2,11 +2,11 @@ public class Marker { - public string Description { get; set; } + public string Description { get; set; } = string.Empty; public double X { get; set; } public double Y { get; set; } public bool ShowPopup { get; set; } -} +} \ No newline at end of file diff --git a/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Point.cs b/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Point.cs index 34194163..054e56ec 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Point.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.ComponentsLibrary/Map/Point.cs @@ -5,4 +5,5 @@ public class Point public double X { get; set; } public double Y { get; set; } -} \ No newline at end of file +} +// Compare this snippet from save-points\01-Components-and-layout\BlazingPizza.ComponentsLibrary\Map\Marker.cs: \ No newline at end of file diff --git a/save-points/04-refactor-state-management/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/save-points/04-refactor-state-management/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml index a1b61965..3f740c74 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml +++ b/save-points/04-refactor-state-management/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -17,7 +17,7 @@ {
    - @User.Identity.Name + @User?.Identity?.Name
    diff --git a/save-points/04-refactor-state-management/BlazingPizza.Server/BlazingPizza.Server.csproj b/save-points/04-refactor-state-management/BlazingPizza.Server/BlazingPizza.Server.csproj index ddfe5f60..a1043cb6 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Server/BlazingPizza.Server.csproj +++ b/save-points/04-refactor-state-management/BlazingPizza.Server/BlazingPizza.Server.csproj @@ -2,7 +2,8 @@ $(TargetFrameworkVersion) - enable + true + enable @@ -14,7 +15,6 @@ - diff --git a/save-points/04-refactor-state-management/BlazingPizza.Server/OrdersController.cs b/save-points/04-refactor-state-management/BlazingPizza.Server/OrdersController.cs index 308f3f64..9e78e746 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Server/OrdersController.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Server/OrdersController.cs @@ -1,9 +1,6 @@ using System.Security.Claims; -using System.Text.Json; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using WebPush; namespace BlazingPizza.Server; @@ -23,7 +20,7 @@ public OrdersController(PizzaStoreContext db) public async Task>> GetOrders() { var orders = await _db.Orders - .Where(o => o.UserId == GetUserId()) + // .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -38,7 +35,7 @@ public async Task> GetOrderWithStatus(int orderId) { var order = await _db.Orders .Where(o => o.OrderId == orderId) - .Where(o => o.UserId == GetUserId()) + // .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -57,19 +54,19 @@ public async Task> PlaceOrder(Order order) { order.CreatedTime = DateTime.Now; order.DeliveryLocation = new LatLong(51.5001, -0.1239); - order.UserId = GetUserId(); + // order.UserId = PizzaApiExtensions.GetUserId(HttpContext); // 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.SpecialId = pizza.Special?.Id ?? 0; pizza.Special = null; foreach (var topping in pizza.Toppings) { - topping.ToppingId = topping.Topping.Id; + topping.ToppingId = topping.Topping?.Id ?? 0; topping.Topping = null; } } @@ -78,7 +75,7 @@ public async Task> PlaceOrder(Order order) await _db.SaveChangesAsync(); // In the background, send push notifications if possible - var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); + var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == PizzaApiExtensions.GetUserId(HttpContext)).SingleOrDefaultAsync(); if (subscription != null) { _ = TrackAndSendNotificationsAsync(order, subscription); @@ -87,11 +84,6 @@ public async Task> PlaceOrder(Order order) return order.OrderId; } - private string GetUserId() - { - return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - } - private static async Task TrackAndSendNotificationsAsync(Order order, NotificationSubscription subscription) { // In a realistic case, some other backend process would track @@ -104,27 +96,9 @@ private static async Task TrackAndSendNotificationsAsync(Order order, Notificati await SendNotificationAsync(order, subscription, "Your order is now delivered. Enjoy!"); } - private static async Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message) + private static 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); - } + // This will be implemented later + return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/save-points/04-refactor-state-management/BlazingPizza.Server/PizzaApiExtensions.cs b/save-points/04-refactor-state-management/BlazingPizza.Server/PizzaApiExtensions.cs index 524ca759..d277597f 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Server/PizzaApiExtensions.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Server/PizzaApiExtensions.cs @@ -19,6 +19,10 @@ public static WebApplication MapPizzaApi(this WebApplication app) // 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(context); + if (userId is null) + { + return Results.Unauthorized(); + } var oldSubscriptions = db.NotificationSubscriptions.Where(e => e.UserId == userId); db.NotificationSubscriptions.RemoveRange(oldSubscriptions); @@ -49,9 +53,6 @@ public static WebApplication MapPizzaApi(this WebApplication app) } - private static string GetUserId(HttpContext context) - { - return context.User.FindFirstValue(ClaimTypes.NameIdentifier); - } + public static string? GetUserId(HttpContext context) => context.User.FindFirstValue(ClaimTypes.NameIdentifier); } \ No newline at end of file diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/Address.cs b/save-points/04-refactor-state-management/BlazingPizza.Shared/Address.cs index f2d1daa4..d1a53375 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/Address.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/Address.cs @@ -1,20 +1,18 @@ -using System.ComponentModel.DataAnnotations; - -namespace BlazingPizza; +namespace BlazingPizza; public class Address { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; - public string Line1 { get; set; } + public string Line1 { get; set; } = string.Empty; - public string Line2 { get; set; } + public string Line2 { get; set; } = string.Empty; - public string City { get; set; } + public string City { get; set; } = string.Empty; - public string Region { get; set; } + public string Region { get; set; } = string.Empty; - public string PostalCode { get; set; } + public string PostalCode { get; set; } = string.Empty; } diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/BlazingPizza.Shared.csproj b/save-points/04-refactor-state-management/BlazingPizza.Shared/BlazingPizza.Shared.csproj index 075ddd60..785c204e 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/BlazingPizza.Shared.csproj +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/BlazingPizza.Shared.csproj @@ -1,13 +1,18 @@  - - $(TargetFrameworkVersion) - BlazingPizza + + $(TargetFrameworkVersion) + BlazingPizza true - + enable + - - - + + + + + + + diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/NotificationSubscription.cs b/save-points/04-refactor-state-management/BlazingPizza.Shared/NotificationSubscription.cs index 5c99e3f4..7f1ea4d9 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/NotificationSubscription.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/NotificationSubscription.cs @@ -2,13 +2,13 @@ public class NotificationSubscription { - public int NotificationSubscriptionId { get; set; } + public int? NotificationSubscriptionId { get; set; } - public string UserId { get; set; } + public string? UserId { get; set; } - public string Url { get; set; } + public string? Url { get; set; } - public string P256dh { get; set; } + public string? P256dh { get; set; } - public string Auth { get; set; } -} + public string? Auth { get; set; } +} \ No newline at end of file diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/Order.cs b/save-points/04-refactor-state-management/BlazingPizza.Shared/Order.cs index 180ec55b..f1211588 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/Order.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/Order.cs @@ -6,13 +6,15 @@ public class Order { public int OrderId { get; set; } - public string UserId { get; set; } + // Set by the server during POST + public string? UserId { get; set; } public DateTime CreatedTime { get; set; } public Address DeliveryAddress { get; set; } = new Address(); - public LatLong DeliveryLocation { get; set; } + // Set by server during POST + public LatLong? DeliveryLocation { get; set; } public List Pizzas { get; set; } = new List(); diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/OrderWithStatus.cs b/save-points/04-refactor-state-management/BlazingPizza.Shared/OrderWithStatus.cs index fbfac7af..c0e3495a 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/OrderWithStatus.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/OrderWithStatus.cs @@ -9,16 +9,19 @@ 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; } + // Set from DB + public Order Order { get; set; } = null!; - public string StatusText { get; set; } + // Set from Order + public string StatusText { get; set; } = null!; public bool IsDelivered => StatusText == "Delivered"; - public List MapMarkers { get; set; } + public List MapMarkers { get; set; } = null!; public static OrderWithStatus FromOrder(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // To simulate a real backend process, we fake status updates based on the amount // of time since the order was placed string statusText; @@ -65,6 +68,7 @@ public static OrderWithStatus FromOrder(Order order) private static LatLong ComputeStartPosition(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // Random but deterministic based on order ID var rng = new Random(order.OrderId); var distance = 0.01 + rng.NextDouble() * 0.02; diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/Pizza.cs b/save-points/04-refactor-state-management/BlazingPizza.Shared/Pizza.cs index 2a7a1c29..150de954 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/Pizza.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/Pizza.cs @@ -15,22 +15,24 @@ public class Pizza public int OrderId { get; set; } - public PizzaSpecial Special { get; set; } + public PizzaSpecial? Special { get; set; } public int SpecialId { get; set; } public int Size { get; set; } - public List Toppings { get; set; } + public List Toppings { get; set; } = new(); public decimal GetBasePrice() { + if(Special == null) throw new NullReferenceException($"{nameof(Special)} was null when calculating Base Price."); return ((decimal)Size / (decimal)DefaultSize) * Special.BasePrice; } public decimal GetTotalPrice() { - return GetBasePrice() + Toppings.Sum(t => t.Topping.Price); + if (Toppings.Any(t => t.Topping is null)) throw new NullReferenceException($"{nameof(Toppings)} contained null when calculating the Total Price."); + return GetBasePrice() + Toppings.Sum(t => t.Topping!.Price); } public string GetFormattedTotalPrice() diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/PizzaSpecial.cs b/save-points/04-refactor-state-management/BlazingPizza.Shared/PizzaSpecial.cs index 1fd9006a..d53ab286 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/PizzaSpecial.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/PizzaSpecial.cs @@ -7,13 +7,13 @@ public class PizzaSpecial { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal BasePrice { get; set; } - public string Description { get; set; } + public string Description { get; set; } = string.Empty; - public string ImageUrl { get; set; } + public string ImageUrl { get; set; } = "img/pizzas/cheese.jpg"; public string GetFormattedBasePrice() => BasePrice.ToString("0.00"); } \ No newline at end of file diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/PizzaTopping.cs b/save-points/04-refactor-state-management/BlazingPizza.Shared/PizzaTopping.cs index 1c062732..724f1c06 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/PizzaTopping.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/PizzaTopping.cs @@ -2,7 +2,7 @@ public class PizzaTopping { - public Topping Topping { get; set; } + public Topping? Topping { get; set; } public int ToppingId { get; set; } diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/Topping.cs b/save-points/04-refactor-state-management/BlazingPizza.Shared/Topping.cs index 4eb67ae9..29b9e790 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/Topping.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/Topping.cs @@ -4,7 +4,7 @@ public class Topping { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal Price { get; set; } diff --git a/save-points/04-refactor-state-management/BlazingPizza.Shared/UserInfo.cs b/save-points/04-refactor-state-management/BlazingPizza.Shared/UserInfo.cs index babb4315..fb0045bf 100644 --- a/save-points/04-refactor-state-management/BlazingPizza.Shared/UserInfo.cs +++ b/save-points/04-refactor-state-management/BlazingPizza.Shared/UserInfo.cs @@ -4,6 +4,6 @@ public class UserInfo { public bool IsAuthenticated { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; } \ No newline at end of file diff --git a/save-points/05-checkout-with-validation/BlazingPizza.Client/BlazingPizza.Client.csproj b/save-points/05-checkout-with-validation/BlazingPizza.Client/BlazingPizza.Client.csproj index 1847e754..48b9455e 100644 --- a/save-points/05-checkout-with-validation/BlazingPizza.Client/BlazingPizza.Client.csproj +++ b/save-points/05-checkout-with-validation/BlazingPizza.Client/BlazingPizza.Client.csproj @@ -1,21 +1,21 @@  - - $(TargetFrameworkVersion) + + $(TargetFrameworkVersion) true - + enable + - - - - - - - + + + + + + - - - - + + + + diff --git a/save-points/05-checkout-with-validation/BlazingPizza.Client/OrderState.cs b/save-points/05-checkout-with-validation/BlazingPizza.Client/OrderState.cs index 00dd3627..daa0f4db 100644 --- a/save-points/05-checkout-with-validation/BlazingPizza.Client/OrderState.cs +++ b/save-points/05-checkout-with-validation/BlazingPizza.Client/OrderState.cs @@ -4,7 +4,7 @@ public class OrderState { public bool ShowingConfigureDialog { get; private set; } - public Pizza ConfiguringPizza { get; private set; } + public Pizza? ConfiguringPizza { get; private set; } public Order Order { get; private set; } = new Order(); @@ -29,8 +29,11 @@ public void CancelConfigurePizzaDialog() public void ConfirmConfigurePizzaDialog() { - Order.Pizzas.Add(ConfiguringPizza); - ConfiguringPizza = null; + if (ConfiguringPizza is not null) + { + Order.Pizzas.Add(ConfiguringPizza); + ConfiguringPizza = null; + } ShowingConfigureDialog = false; } @@ -44,9 +47,4 @@ public void ResetOrder() { Order = new Order(); } - - public void ReplaceOrder(Order order) - { - Order = order; - } } diff --git a/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/Index.razor b/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/Index.razor index e3dbe2ce..613db159 100644 --- a/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/Index.razor +++ b/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/Index.razor @@ -1,11 +1,10 @@ @page "/" @inject HttpClient HttpClient @inject OrderState OrderState -@inject NavigationManager NavigationManager
      - @if (specials != null) + @if (specials is not null) { @foreach (var special in specials) { @@ -56,7 +55,7 @@ } @code { - List specials; + List? specials; Order Order => OrderState.Order; protected override async Task OnInitializedAsync() diff --git a/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/MyOrders.razor b/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/MyOrders.razor index 36d3902e..3c5aefdd 100644 --- a/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/MyOrders.razor +++ b/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/MyOrders.razor @@ -4,7 +4,7 @@ Blazing Pizza - My Orders
      - @if (ordersWithStatus == null) + @if (ordersWithStatus is null) { Loading... } @@ -41,7 +41,7 @@
      @code { - IEnumerable ordersWithStatus; + IEnumerable? ordersWithStatus; protected override async Task OnParametersSetAsync() { diff --git a/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/OrderDetails.razor b/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/OrderDetails.razor index 25e06141..ced45b7c 100644 --- a/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/OrderDetails.razor +++ b/save-points/05-checkout-with-validation/BlazingPizza.Client/Pages/OrderDetails.razor @@ -11,7 +11,7 @@

      Nope

      Sorry, this order could not be loaded.

      } - else if (orderWithStatus == null) + else if (orderWithStatus is null) { Loading... } @@ -38,9 +38,9 @@ @code { [Parameter] public int OrderId { get; set; } - OrderWithStatus orderWithStatus; + OrderWithStatus? orderWithStatus; bool invalidOrder; - CancellationTokenSource pollingCancellationToken; + CancellationTokenSource? pollingCancellationToken; protected override void OnParametersSet() { @@ -59,7 +59,7 @@ try { invalidOrder = false; - orderWithStatus = await HttpClient.GetFromJsonAsync($"orders/{OrderId}"); + orderWithStatus = await HttpClient.GetFromJsonAsync($"orders/{OrderId}") ?? throw new NullReferenceException(); StateHasChanged(); if (orderWithStatus.IsDelivered) diff --git a/save-points/05-checkout-with-validation/BlazingPizza.Client/Shared/AddressEditor.razor b/save-points/05-checkout-with-validation/BlazingPizza.Client/Shared/AddressEditor.razor index 05e76410..67eedef2 100644 --- a/save-points/05-checkout-with-validation/BlazingPizza.Client/Shared/AddressEditor.razor +++ b/save-points/05-checkout-with-validation/BlazingPizza.Client/Shared/AddressEditor.razor @@ -47,5 +47,5 @@
    @code { - [Parameter] public Address Address { get; set; } + [Parameter, EditorRequired] public Address Address { get; set; } = new(); } diff --git a/save-points/05-checkout-with-validation/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor b/save-points/05-checkout-with-validation/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor index 56549454..e643d7b3 100644 --- a/save-points/05-checkout-with-validation/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor +++ b/save-points/05-checkout-with-validation/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor @@ -3,8 +3,8 @@
    -

    @Pizza.Special.Name

    - @Pizza.Special.Description +

    @Pizza.Special?.Name

    + @Pizza.Special?.Description
    @@ -16,7 +16,7 @@
    - @if (toppings == null) + @if (toppings is null) { @@ -41,11 +41,14 @@
    @foreach (var topping in Pizza.Toppings) { -
    - @topping.Topping.Name - @topping.Topping.GetFormattedPrice() - -
    + if (topping?.Topping is not null) + { +
    + @topping.Topping.Name + @topping.Topping.GetFormattedPrice() + +
    + } }
    @@ -61,20 +64,21 @@
    @code { - List toppings; + List? toppings; - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnCancel { get; set; } - [Parameter] public EventCallback OnConfirm { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnCancel { get; set; } + [Parameter, EditorRequired] public EventCallback OnConfirm { get; set; } protected async override Task OnInitializedAsync() { - toppings = await HttpClient.GetFromJsonAsync>("toppings"); + toppings = await HttpClient.GetFromJsonAsync>("toppings") ?? new(); } void ToppingSelected(ChangeEventArgs e) { - if (int.TryParse((string)e.Value, out var index) && index >= 0) + if (toppings is null) return; + if (int.TryParse((string?)e.Value, out var index) && index >= 0) { AddTopping(toppings[index]); } @@ -82,7 +86,7 @@ void AddTopping(Topping topping) { - if (Pizza.Toppings.Find(pt => pt.Topping == topping) == null) + if (Pizza.Toppings.Find(pt => pt.Topping == topping) is null) { Pizza.Toppings.Add(new PizzaTopping() { Topping = topping }); } diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor b/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor index e0e9f41d..f7f7a31d 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor @@ -1,10 +1,10 @@ 
    x -
    @(Pizza.Size)" @Pizza.Special.Name
    +
    @(Pizza.Size)" @Pizza.Special?.Name
      @foreach (var topping in Pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    @@ -13,6 +13,6 @@
    @code { - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnRemoved { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnRemoved { get; set; } } \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/LoginDisplay.razor b/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/LoginDisplay.razor index 4d483eb9..e442a897 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/LoginDisplay.razor +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/LoginDisplay.razor @@ -1,5 +1,4 @@ @inject NavigationManager Navigation -@inject SignOutSessionStateManager SignOutManager @code{ - async Task BeginSignOut() + void BeginSignOut() { - await SignOutManager.SetSignOutState(); - Navigation.NavigateTo("authentication/logout"); + Navigation.NavigateToLogout("authentication/logout"); } } \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/OrderReview.razor b/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/OrderReview.razor index e1424934..56668721 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/OrderReview.razor +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/OrderReview.razor @@ -3,7 +3,7 @@

    @(pizza.Size)" - @pizza.Special.Name + @pizza.Special?.Name (£@pizza.GetFormattedTotalPrice())

    @@ -11,7 +11,7 @@
      @foreach (var topping in pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    } @@ -24,5 +24,5 @@

    @code { - [Parameter] public Order Order { get; set; } + [Parameter, EditorRequired] public Order Order { get; set; } = new(); } diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/RedirectToLogin.razor b/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/RedirectToLogin.razor index 16c4bea2..696c387d 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/RedirectToLogin.razor +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Client/Shared/RedirectToLogin.razor @@ -2,6 +2,6 @@ @code { protected override void OnInitialized() { - Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}"); + Navigation.NavigateToLogin($"authentication/login"); } } \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj b/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj index cb996b1b..a907e880 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj +++ b/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj @@ -2,7 +2,8 @@ $(TargetFrameworkVersion) - true + true + enable diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Map.razor b/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Map.razor index c2413ac4..4325755e 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Map.razor +++ b/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Map.razor @@ -5,9 +5,9 @@ @code { string elementId = $"map-{Guid.NewGuid().ToString("D")}"; - + [Parameter] public double Zoom { get; set; } - [Parameter] public List Markers { get; set; } + [Parameter, EditorRequired] public List Markers { get; set; } = new(); protected async override Task OnAfterRenderAsync(bool firstRender) { diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Marker.cs b/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Marker.cs index b067719c..d4dcdbad 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Marker.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Marker.cs @@ -2,11 +2,11 @@ public class Marker { - public string Description { get; set; } + public string Description { get; set; } = string.Empty; public double X { get; set; } public double Y { get; set; } public bool ShowPopup { get; set; } -} +} \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Point.cs b/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Point.cs index 34194163..054e56ec 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Point.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.ComponentsLibrary/Map/Point.cs @@ -5,4 +5,5 @@ public class Point public double X { get; set; } public double Y { get; set; } -} \ No newline at end of file +} +// Compare this snippet from save-points\01-Components-and-layout\BlazingPizza.ComponentsLibrary\Map\Marker.cs: \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/save-points/06-authentication-and-authorization/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml index a1b61965..3f740c74 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -17,7 +17,7 @@ {
    - @User.Identity.Name + @User?.Identity?.Name
    diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Server/BlazingPizza.Server.csproj b/save-points/06-authentication-and-authorization/BlazingPizza.Server/BlazingPizza.Server.csproj index 227890e1..a1043cb6 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Server/BlazingPizza.Server.csproj +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Server/BlazingPizza.Server.csproj @@ -3,6 +3,7 @@ $(TargetFrameworkVersion) true + enable diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Server/NotificationsController.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Server/NotificationsController.cs index bb3b96ff..43633d88 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Server/NotificationsController.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Server/NotificationsController.cs @@ -23,7 +23,11 @@ public async Task Subscribe(NotificationSubscription s { // 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 userId = PizzaApiExtensions.GetUserId(HttpContext); + if (userId is null) + { + throw new UnauthorizedAccessException(); + } var oldSubscriptions = _db.NotificationSubscriptions.Where(e => e.UserId == userId); _db.NotificationSubscriptions.RemoveRange(oldSubscriptions); @@ -35,9 +39,5 @@ public async Task Subscribe(NotificationSubscription s return subscription; } - private string GetUserId() - { - return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - } } } diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Server/OidcConfigurationController.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Server/OidcConfigurationController.cs index 50a0a8ad..1431697e 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Server/OidcConfigurationController.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Server/OidcConfigurationController.cs @@ -1,26 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; using Microsoft.AspNetCore.Mvc; -namespace BlazingPizza.Server +namespace BlazingPizza.Server; + +public class OidcConfigurationController : Controller { - public class OidcConfigurationController : Controller + public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider) { - public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider) - { - ClientRequestParametersProvider = clientRequestParametersProvider; - } + ClientRequestParametersProvider = clientRequestParametersProvider; + } - public IClientRequestParametersProvider ClientRequestParametersProvider { get; } + public IClientRequestParametersProvider ClientRequestParametersProvider { get; } - [HttpGet("_configuration/{clientId}")] - public IActionResult GetClientRequestParameters([FromRoute] string clientId) - { - var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId); - return Ok(parameters); - } + [HttpGet("_configuration/{clientId}")] + public IActionResult GetClientRequestParameters([FromRoute] string clientId) + { + var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId); + return Ok(parameters); } } diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Server/OrdersController.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Server/OrdersController.cs index 72b59890..c98f4aee 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Server/OrdersController.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Server/OrdersController.cs @@ -7,109 +7,103 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace BlazingPizza.Server +namespace BlazingPizza.Server; + +[Route("orders")] +[ApiController] +[Authorize] +public class OrdersController : Controller { - [Route("orders")] - [ApiController] - [Authorize] - public class OrdersController : Controller - { - private readonly PizzaStoreContext _db; + private readonly PizzaStoreContext _db; - public OrdersController(PizzaStoreContext db) - { - _db = db; - } + public OrdersController(PizzaStoreContext db) + { + _db = db; + } - [HttpGet] - public async Task>> GetOrders() - { - var orders = await _db.Orders - .Where(o => o.UserId == GetUserId()) + [HttpGet] + public async Task>> GetOrders() + { + var orders = await _db.Orders + .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .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(o => OrderWithStatus.FromOrder(o)).ToList(); - } + return orders.Select(o => OrderWithStatus.FromOrder(o)).ToList(); + } - [HttpGet("{orderId}")] - public async Task> GetOrderWithStatus(int orderId) - { - var order = await _db.Orders + [HttpGet("{orderId}")] + public async Task> GetOrderWithStatus(int orderId) + { + var order = await _db.Orders .Where(o => o.OrderId == orderId) - .Where(o => o.UserId == GetUserId()) + .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .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(); - if (order == null) - { - return NotFound(); - } - - return OrderWithStatus.FromOrder(order); + if (order == null) + { + return NotFound(); } - [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; - } - } + return OrderWithStatus.FromOrder(order); + } - _db.Orders.Attach(order); - await _db.SaveChangesAsync(); + [HttpPost] + public async Task> PlaceOrder(Order order) + { + order.CreatedTime = DateTime.Now; + order.DeliveryLocation = new LatLong(51.5001, -0.1239); + order.UserId = PizzaApiExtensions.GetUserId(HttpContext); + + // 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 ?? 0; + pizza.Special = null; - // In the background, send push notifications if possible - var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); - if (subscription != null) + foreach (var topping in pizza.Toppings) { - _ = TrackAndSendNotificationsAsync(order, subscription); + topping.ToppingId = topping.Topping?.Id ?? 0; + topping.Topping = null; } - - return order.OrderId; } - private string GetUserId() - { - return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - } + _db.Orders.Attach(order); + await _db.SaveChangesAsync(); - private static async Task TrackAndSendNotificationsAsync(Order order, NotificationSubscription subscription) + // In the background, send push notifications if possible + var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == PizzaApiExtensions.GetUserId(HttpContext)).SingleOrDefaultAsync(); + if (subscription != null) { - // 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!"); + _ = TrackAndSendNotificationsAsync(order, subscription); } - private static Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message) - { - // This will be implemented later - return Task.CompletedTask; - } + return order.OrderId; + } + + 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 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 Task SendNotificationAsync(Order order, NotificationSubscription subscription, string message) + { + // This will be implemented later + return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaApiExtensions.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaApiExtensions.cs index 524ca759..d277597f 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaApiExtensions.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaApiExtensions.cs @@ -19,6 +19,10 @@ public static WebApplication MapPizzaApi(this WebApplication app) // 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(context); + if (userId is null) + { + return Results.Unauthorized(); + } var oldSubscriptions = db.NotificationSubscriptions.Where(e => e.UserId == userId); db.NotificationSubscriptions.RemoveRange(oldSubscriptions); @@ -49,9 +53,6 @@ public static WebApplication MapPizzaApi(this WebApplication app) } - private static string GetUserId(HttpContext context) - { - return context.User.FindFirstValue(ClaimTypes.NameIdentifier); - } + public static string? GetUserId(HttpContext context) => context.User.FindFirstValue(ClaimTypes.NameIdentifier); } \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaStoreContext.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaStoreContext.cs index 4830d1a4..50ac87e4 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaStoreContext.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaStoreContext.cs @@ -3,37 +3,36 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; -namespace BlazingPizza.Server +namespace BlazingPizza.Server; + +public class PizzaStoreContext : ApiAuthorizationDbContext { - public class PizzaStoreContext : ApiAuthorizationDbContext - { - public PizzaStoreContext( + public PizzaStoreContext( DbContextOptions options, IOptions operationalStoreOptions) : base(options, operationalStoreOptions) - { - } + { + } - public DbSet Orders { get; set; } + public DbSet Orders { get; set; } - public DbSet Pizzas { get; set; } + public DbSet Pizzas { get; set; } - public DbSet Specials { get; set; } + public DbSet Specials { get; set; } - public DbSet Toppings { get; set; } + public DbSet Toppings { get; set; } - public DbSet NotificationSubscriptions { get; set; } + public DbSet NotificationSubscriptions { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); + 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(); + // 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); - } + // Inline the Lat-Long pairs in Order rather than having a FK to another table + modelBuilder.Entity().OwnsOne(o => o.DeliveryLocation); } -} \ No newline at end of file +} diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaStoreUser.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaStoreUser.cs index 171ce83d..7ab40235 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Server/PizzaStoreUser.cs +++ b/save-points/06-authentication-and-authorization/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 { } \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Address.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Address.cs index a671359a..15e546c1 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Address.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Address.cs @@ -5,20 +5,20 @@ public class Address public int Id { get; set; } [Required, MaxLength(100)] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; [Required, MaxLength(100)] - public string Line1 { get; set; } + public string Line1 { get; set; } = string.Empty; [MaxLength(100)] - public string Line2 { get; set; } + public string Line2 { get; set; } = string.Empty; [Required, MaxLength(50)] - public string City { get; set; } + public string City { get; set; } = string.Empty; [Required, MaxLength(20)] - public string Region { get; set; } + public string Region { get; set; } = string.Empty; [Required, MaxLength(20)] - public string PostalCode { get; set; } + public string PostalCode { get; set; } = string.Empty; } diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/BlazingPizza.Shared.csproj b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/BlazingPizza.Shared.csproj index fb17e53b..785c204e 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/BlazingPizza.Shared.csproj +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/BlazingPizza.Shared.csproj @@ -1,17 +1,18 @@  - - $(TargetFrameworkVersion) - BlazingPizza + + $(TargetFrameworkVersion) + BlazingPizza true - + enable + - - - - + + + + diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/NotificationSubscription.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/NotificationSubscription.cs index 5c99e3f4..7f1ea4d9 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/NotificationSubscription.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/NotificationSubscription.cs @@ -2,13 +2,13 @@ public class NotificationSubscription { - public int NotificationSubscriptionId { get; set; } + public int? NotificationSubscriptionId { get; set; } - public string UserId { get; set; } + public string? UserId { get; set; } - public string Url { get; set; } + public string? Url { get; set; } - public string P256dh { get; set; } + public string? P256dh { get; set; } - public string Auth { get; set; } -} + public string? Auth { get; set; } +} \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Order.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Order.cs index 180ec55b..f1211588 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Order.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Order.cs @@ -6,13 +6,15 @@ public class Order { public int OrderId { get; set; } - public string UserId { get; set; } + // Set by the server during POST + public string? UserId { get; set; } public DateTime CreatedTime { get; set; } public Address DeliveryAddress { get; set; } = new Address(); - public LatLong DeliveryLocation { get; set; } + // Set by server during POST + public LatLong? DeliveryLocation { get; set; } public List Pizzas { get; set; } = new List(); diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/OrderWithStatus.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/OrderWithStatus.cs index fbfac7af..c0e3495a 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/OrderWithStatus.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/OrderWithStatus.cs @@ -9,16 +9,19 @@ 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; } + // Set from DB + public Order Order { get; set; } = null!; - public string StatusText { get; set; } + // Set from Order + public string StatusText { get; set; } = null!; public bool IsDelivered => StatusText == "Delivered"; - public List MapMarkers { get; set; } + public List MapMarkers { get; set; } = null!; public static OrderWithStatus FromOrder(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // To simulate a real backend process, we fake status updates based on the amount // of time since the order was placed string statusText; @@ -65,6 +68,7 @@ public static OrderWithStatus FromOrder(Order order) private static LatLong ComputeStartPosition(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // Random but deterministic based on order ID var rng = new Random(order.OrderId); var distance = 0.01 + rng.NextDouble() * 0.02; diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Pizza.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Pizza.cs index 2a7a1c29..150de954 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Pizza.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Pizza.cs @@ -15,22 +15,24 @@ public class Pizza public int OrderId { get; set; } - public PizzaSpecial Special { get; set; } + public PizzaSpecial? Special { get; set; } public int SpecialId { get; set; } public int Size { get; set; } - public List Toppings { get; set; } + public List Toppings { get; set; } = new(); public decimal GetBasePrice() { + if(Special == null) throw new NullReferenceException($"{nameof(Special)} was null when calculating Base Price."); return ((decimal)Size / (decimal)DefaultSize) * Special.BasePrice; } public decimal GetTotalPrice() { - return GetBasePrice() + Toppings.Sum(t => t.Topping.Price); + if (Toppings.Any(t => t.Topping is null)) throw new NullReferenceException($"{nameof(Toppings)} contained null when calculating the Total Price."); + return GetBasePrice() + Toppings.Sum(t => t.Topping!.Price); } public string GetFormattedTotalPrice() diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/PizzaSpecial.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/PizzaSpecial.cs index 1fd9006a..d53ab286 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/PizzaSpecial.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/PizzaSpecial.cs @@ -7,13 +7,13 @@ public class PizzaSpecial { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal BasePrice { get; set; } - public string Description { get; set; } + public string Description { get; set; } = string.Empty; - public string ImageUrl { get; set; } + public string ImageUrl { get; set; } = "img/pizzas/cheese.jpg"; public string GetFormattedBasePrice() => BasePrice.ToString("0.00"); } \ No newline at end of file diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/PizzaTopping.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/PizzaTopping.cs index 1c062732..724f1c06 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/PizzaTopping.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/PizzaTopping.cs @@ -2,7 +2,7 @@ public class PizzaTopping { - public Topping Topping { get; set; } + public Topping? Topping { get; set; } public int ToppingId { get; set; } diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Topping.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Topping.cs index 4eb67ae9..29b9e790 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Topping.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/Topping.cs @@ -4,7 +4,7 @@ public class Topping { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal Price { get; set; } diff --git a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/UserInfo.cs b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/UserInfo.cs index babb4315..fb0045bf 100644 --- a/save-points/06-authentication-and-authorization/BlazingPizza.Shared/UserInfo.cs +++ b/save-points/06-authentication-and-authorization/BlazingPizza.Shared/UserInfo.cs @@ -4,6 +4,6 @@ public class UserInfo { public bool IsAuthenticated { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; } \ No newline at end of file diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/App.razor b/save-points/07-javascript-interop/BlazingPizza.Client/App.razor index 815659c4..5b6fc0fa 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/App.razor +++ b/save-points/07-javascript-interop/BlazingPizza.Client/App.razor @@ -1,6 +1,6 @@  - - + + @@ -9,11 +9,11 @@
    Please wait...
    -
    - - -
    Sorry, there's nothing at this address.
    -
    -
    -
    +
    + + +
    Sorry, there's nothing at this address.
    +
    +
    +
    diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/BlazingPizza.Client.csproj b/save-points/07-javascript-interop/BlazingPizza.Client/BlazingPizza.Client.csproj index 1847e754..48b9455e 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/BlazingPizza.Client.csproj +++ b/save-points/07-javascript-interop/BlazingPizza.Client/BlazingPizza.Client.csproj @@ -1,21 +1,21 @@  - - $(TargetFrameworkVersion) + + $(TargetFrameworkVersion) true - + enable + - - - - - - - + + + + + + - - - - + + + + diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/OrderState.cs b/save-points/07-javascript-interop/BlazingPizza.Client/OrderState.cs index 00dd3627..991a7512 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/OrderState.cs +++ b/save-points/07-javascript-interop/BlazingPizza.Client/OrderState.cs @@ -4,7 +4,7 @@ public class OrderState { public bool ShowingConfigureDialog { get; private set; } - public Pizza ConfiguringPizza { get; private set; } + public Pizza? ConfiguringPizza { get; private set; } public Order Order { get; private set; } = new Order(); @@ -29,8 +29,11 @@ public void CancelConfigurePizzaDialog() public void ConfirmConfigurePizzaDialog() { - Order.Pizzas.Add(ConfiguringPizza); - ConfiguringPizza = null; + if (ConfiguringPizza is not null) + { + Order.Pizzas.Add(ConfiguringPizza); + ConfiguringPizza = null; + } ShowingConfigureDialog = false; } diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/OrdersClient.cs b/save-points/07-javascript-interop/BlazingPizza.Client/OrdersClient.cs index b4a08f1e..8dd911c3 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/OrdersClient.cs +++ b/save-points/07-javascript-interop/BlazingPizza.Client/OrdersClient.cs @@ -1,5 +1,4 @@ using System.Net.Http.Json; -using System.Text.Json; namespace BlazingPizza.Client; @@ -13,11 +12,11 @@ public OrdersClient(HttpClient httpClient) } public async Task> GetOrders() => - await httpClient.GetFromJsonAsync("orders", OrderContext.Default.ListOrderWithStatus); + await httpClient.GetFromJsonAsync("orders", OrderContext.Default.ListOrderWithStatus) ?? new(); public async Task GetOrder(int orderId) => - await httpClient.GetFromJsonAsync($"orders/{orderId}", OrderContext.Default.OrderWithStatus); + await httpClient.GetFromJsonAsync($"orders/{orderId}", OrderContext.Default.OrderWithStatus) ?? new(); public async Task PlaceOrder(Order order) @@ -28,9 +27,4 @@ public async Task PlaceOrder(Order order) return orderId; } - public async Task SubscribeToNotifications(NotificationSubscription subscription) - { - var response = await httpClient.PutAsJsonAsync("notifications/subscribe", subscription); - response.EnsureSuccessStatusCode(); - } } diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Authentication.razor b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Authentication.razor index 5691b4b0..c7266152 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Authentication.razor +++ b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Authentication.razor @@ -1,6 +1,5 @@ @page "/authentication/{action}" @inject OrderState OrderState -@inject NavigationManager NavigationManager @code{ - [Parameter] public string Action { get; set; } + [Parameter] public string Action { get; set; } = string.Empty; public PizzaAuthenticationState RemoteAuthenticationState { get; set; } = new PizzaAuthenticationState(); @@ -24,9 +23,9 @@ private void RestorePizza(PizzaAuthenticationState pizzaState) { - if (pizzaState.Order != null) + if (pizzaState.Order is not null) { OrderState.ReplaceOrder(pizzaState.Order); } } -} \ No newline at end of file +} diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Checkout.razor b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Checkout.razor index 44b01467..e7a194f5 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Checkout.razor +++ b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Checkout.razor @@ -38,9 +38,9 @@ try { var newOrderId = await OrdersClient.PlaceOrder(OrderState.Order); - OrderState.ResetOrder(); - NavigationManager.NavigateTo($"myorders/{newOrderId}"); - } + OrderState.ResetOrder(); + NavigationManager.NavigateTo($"myorders/{newOrderId}"); + } catch (AccessTokenNotAvailableException ex) { ex.Redirect(); diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Index.razor b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Index.razor index 26b1fe38..501f44d4 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Index.razor +++ b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/Index.razor @@ -1,12 +1,11 @@ @page "/" @inject HttpClient HttpClient @inject OrderState OrderState -@inject NavigationManager NavigationManager @inject IJSRuntime JS
      - @if (specials != null) + @if (specials is not null) { @foreach (var special in specials) { @@ -57,7 +56,7 @@ } @code { - List specials; + List? specials; Order Order => OrderState.Order; protected override async Task OnInitializedAsync() @@ -67,7 +66,7 @@ async Task RemovePizza(Pizza configuredPizza) { - if (await JS.Confirm($"Remove {configuredPizza.Special.Name} pizza from the order?")) + if (await JS.Confirm($"Remove {configuredPizza.Special?.Name} pizza from the order?")) { OrderState.RemoveConfiguredPizza(configuredPizza); } diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/MyOrders.razor b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/MyOrders.razor index b28dedeb..da479989 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/MyOrders.razor +++ b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/MyOrders.razor @@ -1,12 +1,11 @@ @page "/myorders" @attribute [Authorize] @inject OrdersClient OrdersClient -@inject NavigationManager NavigationManager Blazing Pizza - My Orders
      - @if (ordersWithStatus == null) + @if (ordersWithStatus is null) { Loading... } @@ -43,7 +42,7 @@
      @code { - IEnumerable ordersWithStatus; + IEnumerable? ordersWithStatus; protected override async Task OnParametersSetAsync() { diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/OrderDetails.razor b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/OrderDetails.razor index 548bbd59..79c18741 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/Pages/OrderDetails.razor +++ b/save-points/07-javascript-interop/BlazingPizza.Client/Pages/OrderDetails.razor @@ -2,7 +2,6 @@ @attribute [Authorize] @using System.Threading @inject OrdersClient OrdersClient -@inject NavigationManager NavigationManager @implements IDisposable Blazing Pizza - Order Details @@ -13,7 +12,7 @@

      Nope

      Sorry, this order could not be loaded.

      } - else if (orderWithStatus == null) + else if (orderWithStatus is null) { Loading... } @@ -43,9 +42,9 @@ @code { [Parameter] public int OrderId { get; set; } - OrderWithStatus orderWithStatus; + OrderWithStatus? orderWithStatus; bool invalidOrder; - CancellationTokenSource pollingCancellationToken; + CancellationTokenSource? pollingCancellationToken; protected override void OnParametersSet() { diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/PizzaAuthenticationState.cs b/save-points/07-javascript-interop/BlazingPizza.Client/PizzaAuthenticationState.cs index 78627625..589d0620 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/PizzaAuthenticationState.cs +++ b/save-points/07-javascript-interop/BlazingPizza.Client/PizzaAuthenticationState.cs @@ -4,5 +4,5 @@ namespace BlazingPizza.Client; public class PizzaAuthenticationState : RemoteAuthenticationState { - public Order Order { get; set; } + public Order? Order { get; set; } } diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/Shared/AddressEditor.razor b/save-points/07-javascript-interop/BlazingPizza.Client/Shared/AddressEditor.razor index 05e76410..fb36ff3a 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/Shared/AddressEditor.razor +++ b/save-points/07-javascript-interop/BlazingPizza.Client/Shared/AddressEditor.razor @@ -47,5 +47,5 @@
    @code { - [Parameter] public Address Address { get; set; } -} + [Parameter, EditorRequired] public Address Address { get; set; } = new(); +} \ No newline at end of file diff --git a/save-points/07-javascript-interop/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor b/save-points/07-javascript-interop/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor index 56549454..e643d7b3 100644 --- a/save-points/07-javascript-interop/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor +++ b/save-points/07-javascript-interop/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor @@ -3,8 +3,8 @@
    -

    @Pizza.Special.Name

    - @Pizza.Special.Description +

    @Pizza.Special?.Name

    + @Pizza.Special?.Description
    @@ -16,7 +16,7 @@
    - @if (toppings == null) + @if (toppings is null) { - - @(Pizza.Size)" (£@(Pizza.GetFormattedTotalPrice())) - -
    -
    - - @if (toppings == null) - { - - } - else if (Pizza.Toppings.Count >= 6) - { -
    (maximum reached)
    - } - else - { - + + @(Pizza.Size)" (£@(Pizza.GetFormattedTotalPrice())) + +
    +
    + + @if (toppings is null) { - + } - - } -
    + else if (Pizza.Toppings.Count >= 6) + { +
    (maximum reached)
    + } + else + { + + } +
    -
    - @foreach (var topping in Pizza.Toppings) - { -
    - @topping.Topping.Name - @topping.Topping.GetFormattedPrice() - +
    + @foreach (var topping in Pizza.Toppings) + { + if (topping?.Topping is not null) + { +
    + @topping.Topping.Name + @topping.Topping.GetFormattedPrice() + +
    + } + }
    - } -
    - + + +
    + + + Price: @(Pizza.GetFormattedTotalPrice()) + + +
    -
    - - - Price: @(Pizza.GetFormattedTotalPrice()) - - -
    @code { - List toppings; + List? toppings; - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnCancel { get; set; } - [Parameter] public EventCallback OnConfirm { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnCancel { get; set; } + [Parameter, EditorRequired] public EventCallback OnConfirm { get; set; } protected async override Task OnInitializedAsync() { - toppings = await HttpClient.GetFromJsonAsync>("toppings"); + toppings = await HttpClient.GetFromJsonAsync>("toppings") ?? new(); } void ToppingSelected(ChangeEventArgs e) { - if (int.TryParse((string)e.Value, out var index) && index >= 0) + if (toppings is null) return; + if (int.TryParse((string?)e.Value, out var index) && index >= 0) { AddTopping(toppings[index]); } @@ -78,7 +84,7 @@ void AddTopping(Topping topping) { - if (Pizza.Toppings.Find(pt => pt.Topping == topping) == null) + if (Pizza.Toppings.Find(pt => pt.Topping == topping) is null) { Pizza.Toppings.Add(new PizzaTopping() { Topping = topping }); } diff --git a/save-points/08-templated-components/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor b/save-points/08-templated-components/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor index e0e9f41d..f7f7a31d 100644 --- a/save-points/08-templated-components/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor +++ b/save-points/08-templated-components/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor @@ -1,10 +1,10 @@ 
    x -
    @(Pizza.Size)" @Pizza.Special.Name
    +
    @(Pizza.Size)" @Pizza.Special?.Name
      @foreach (var topping in Pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    @@ -13,6 +13,6 @@
    @code { - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnRemoved { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnRemoved { get; set; } } \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.Client/Shared/LoginDisplay.razor b/save-points/08-templated-components/BlazingPizza.Client/Shared/LoginDisplay.razor index 4d483eb9..b5c5bfa7 100644 --- a/save-points/08-templated-components/BlazingPizza.Client/Shared/LoginDisplay.razor +++ b/save-points/08-templated-components/BlazingPizza.Client/Shared/LoginDisplay.razor @@ -1,5 +1,4 @@ @inject NavigationManager Navigation -@inject SignOutSessionStateManager SignOutManager -@code{ - async Task BeginSignOut() +@code { + void BeginSignOut() { - await SignOutManager.SetSignOutState(); - Navigation.NavigateTo("authentication/logout"); + Navigation.NavigateToLogout("authentication/logout"); } } \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.Client/Shared/OrderReview.razor b/save-points/08-templated-components/BlazingPizza.Client/Shared/OrderReview.razor index e1424934..56668721 100644 --- a/save-points/08-templated-components/BlazingPizza.Client/Shared/OrderReview.razor +++ b/save-points/08-templated-components/BlazingPizza.Client/Shared/OrderReview.razor @@ -3,7 +3,7 @@

    @(pizza.Size)" - @pizza.Special.Name + @pizza.Special?.Name (£@pizza.GetFormattedTotalPrice())

    @@ -11,7 +11,7 @@
      @foreach (var topping in pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    } @@ -24,5 +24,5 @@

    @code { - [Parameter] public Order Order { get; set; } + [Parameter, EditorRequired] public Order Order { get; set; } = new(); } diff --git a/save-points/08-templated-components/BlazingPizza.Client/Shared/RedirectToLogin.razor b/save-points/08-templated-components/BlazingPizza.Client/Shared/RedirectToLogin.razor index 16c4bea2..696c387d 100644 --- a/save-points/08-templated-components/BlazingPizza.Client/Shared/RedirectToLogin.razor +++ b/save-points/08-templated-components/BlazingPizza.Client/Shared/RedirectToLogin.razor @@ -2,6 +2,6 @@ @code { protected override void OnInitialized() { - Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}"); + Navigation.NavigateToLogin($"authentication/login"); } } \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.Client/_Imports.razor b/save-points/08-templated-components/BlazingPizza.Client/_Imports.razor index b575d7f7..af8847ae 100644 --- a/save-points/08-templated-components/BlazingPizza.Client/_Imports.razor +++ b/save-points/08-templated-components/BlazingPizza.Client/_Imports.razor @@ -12,4 +12,4 @@ @using BlazingPizza.Client.Shared @using BlazingPizza.ComponentsLibrary @using BlazingPizza.ComponentsLibrary.Map -@using BlazingComponents +@using BlazingComponents \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj b/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj index cb996b1b..a907e880 100644 --- a/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj +++ b/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj @@ -2,7 +2,8 @@ $(TargetFrameworkVersion) - true + true + enable diff --git a/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Map.razor b/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Map.razor index c2413ac4..4325755e 100644 --- a/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Map.razor +++ b/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Map.razor @@ -5,9 +5,9 @@ @code { string elementId = $"map-{Guid.NewGuid().ToString("D")}"; - + [Parameter] public double Zoom { get; set; } - [Parameter] public List Markers { get; set; } + [Parameter, EditorRequired] public List Markers { get; set; } = new(); protected async override Task OnAfterRenderAsync(bool firstRender) { diff --git a/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Marker.cs b/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Marker.cs index b067719c..d4dcdbad 100644 --- a/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Marker.cs +++ b/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Marker.cs @@ -2,11 +2,11 @@ public class Marker { - public string Description { get; set; } + public string Description { get; set; } = string.Empty; public double X { get; set; } public double Y { get; set; } public bool ShowPopup { get; set; } -} +} \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Point.cs b/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Point.cs index 34194163..054e56ec 100644 --- a/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Point.cs +++ b/save-points/08-templated-components/BlazingPizza.ComponentsLibrary/Map/Point.cs @@ -5,4 +5,5 @@ public class Point public double X { get; set; } public double Y { get; set; } -} \ No newline at end of file +} +// Compare this snippet from save-points\01-Components-and-layout\BlazingPizza.ComponentsLibrary\Map\Marker.cs: \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/save-points/08-templated-components/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml index a1b61965..3f740c74 100644 --- a/save-points/08-templated-components/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml +++ b/save-points/08-templated-components/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -17,7 +17,7 @@ {
    - @User.Identity.Name + @User?.Identity?.Name
    diff --git a/save-points/08-templated-components/BlazingPizza.Server/BlazingPizza.Server.csproj b/save-points/08-templated-components/BlazingPizza.Server/BlazingPizza.Server.csproj index abd2317b..b028c771 100644 --- a/save-points/08-templated-components/BlazingPizza.Server/BlazingPizza.Server.csproj +++ b/save-points/08-templated-components/BlazingPizza.Server/BlazingPizza.Server.csproj @@ -1,25 +1,26 @@  - - $(TargetFrameworkVersion) + + $(TargetFrameworkVersion) true - + enable + - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + diff --git a/save-points/08-templated-components/BlazingPizza.Server/NotificationsController.cs b/save-points/08-templated-components/BlazingPizza.Server/NotificationsController.cs new file mode 100644 index 00000000..43633d88 --- /dev/null +++ b/save-points/08-templated-components/BlazingPizza.Server/NotificationsController.cs @@ -0,0 +1,43 @@ +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 = PizzaApiExtensions.GetUserId(HttpContext); + if (userId is null) + { + throw new UnauthorizedAccessException(); + } + 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; + } + + } +} diff --git a/save-points/08-templated-components/BlazingPizza.Server/OrdersController.cs b/save-points/08-templated-components/BlazingPizza.Server/OrdersController.cs index 3d2ba63f..adf19b41 100644 --- a/save-points/08-templated-components/BlazingPizza.Server/OrdersController.cs +++ b/save-points/08-templated-components/BlazingPizza.Server/OrdersController.cs @@ -23,7 +23,7 @@ public OrdersController(PizzaStoreContext db) public async Task>> GetOrders() { var orders = await _db.Orders - .Where(o => o.UserId == GetUserId()) + .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -38,7 +38,7 @@ public async Task> GetOrderWithStatus(int orderId) { var order = await _db.Orders .Where(o => o.OrderId == orderId) - .Where(o => o.UserId == GetUserId()) + .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -57,19 +57,19 @@ public async Task> PlaceOrder(Order order) { order.CreatedTime = DateTime.Now; order.DeliveryLocation = new LatLong(51.5001, -0.1239); - order.UserId = GetUserId(); + order.UserId = PizzaApiExtensions.GetUserId(HttpContext); // 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.SpecialId = pizza.Special?.Id ?? 0; pizza.Special = null; foreach (var topping in pizza.Toppings) { - topping.ToppingId = topping.Topping.Id; + topping.ToppingId = topping.Topping?.Id ?? 0; topping.Topping = null; } } @@ -78,7 +78,7 @@ public async Task> PlaceOrder(Order order) await _db.SaveChangesAsync(); // In the background, send push notifications if possible - var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); + var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == PizzaApiExtensions.GetUserId(HttpContext)).SingleOrDefaultAsync(); if (subscription != null) { _ = TrackAndSendNotificationsAsync(order, subscription); @@ -87,11 +87,6 @@ public async Task> PlaceOrder(Order order) return order.OrderId; } - private string GetUserId() - { - return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - } - private static async Task TrackAndSendNotificationsAsync(Order order, NotificationSubscription subscription) { // In a realistic case, some other backend process would track @@ -123,8 +118,8 @@ private static async Task SendNotificationAsync(Order order, NotificationSubscri await webPushClient.SendNotificationAsync(pushSubscription, payload, vapidDetails); } catch (Exception ex) - { + { Console.Error.WriteLine("Error sending push notification: " + ex.Message); } } -} +} \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.Server/PizzaApiExtensions.cs b/save-points/08-templated-components/BlazingPizza.Server/PizzaApiExtensions.cs index 524ca759..d277597f 100644 --- a/save-points/08-templated-components/BlazingPizza.Server/PizzaApiExtensions.cs +++ b/save-points/08-templated-components/BlazingPizza.Server/PizzaApiExtensions.cs @@ -19,6 +19,10 @@ public static WebApplication MapPizzaApi(this WebApplication app) // 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(context); + if (userId is null) + { + return Results.Unauthorized(); + } var oldSubscriptions = db.NotificationSubscriptions.Where(e => e.UserId == userId); db.NotificationSubscriptions.RemoveRange(oldSubscriptions); @@ -49,9 +53,6 @@ public static WebApplication MapPizzaApi(this WebApplication app) } - private static string GetUserId(HttpContext context) - { - return context.User.FindFirstValue(ClaimTypes.NameIdentifier); - } + public static string? GetUserId(HttpContext context) => context.User.FindFirstValue(ClaimTypes.NameIdentifier); } \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.Shared/Address.cs b/save-points/08-templated-components/BlazingPizza.Shared/Address.cs index a671359a..15e546c1 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/Address.cs +++ b/save-points/08-templated-components/BlazingPizza.Shared/Address.cs @@ -5,20 +5,20 @@ public class Address public int Id { get; set; } [Required, MaxLength(100)] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; [Required, MaxLength(100)] - public string Line1 { get; set; } + public string Line1 { get; set; } = string.Empty; [MaxLength(100)] - public string Line2 { get; set; } + public string Line2 { get; set; } = string.Empty; [Required, MaxLength(50)] - public string City { get; set; } + public string City { get; set; } = string.Empty; [Required, MaxLength(20)] - public string Region { get; set; } + public string Region { get; set; } = string.Empty; [Required, MaxLength(20)] - public string PostalCode { get; set; } + public string PostalCode { get; set; } = string.Empty; } diff --git a/save-points/08-templated-components/BlazingPizza.Shared/BlazingPizza.Shared.csproj b/save-points/08-templated-components/BlazingPizza.Shared/BlazingPizza.Shared.csproj index fb17e53b..785c204e 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/BlazingPizza.Shared.csproj +++ b/save-points/08-templated-components/BlazingPizza.Shared/BlazingPizza.Shared.csproj @@ -1,17 +1,18 @@  - - $(TargetFrameworkVersion) - BlazingPizza + + $(TargetFrameworkVersion) + BlazingPizza true - + enable + - - - - + + + + diff --git a/save-points/08-templated-components/BlazingPizza.Shared/NotificationSubscription.cs b/save-points/08-templated-components/BlazingPizza.Shared/NotificationSubscription.cs index 5c99e3f4..7f1ea4d9 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/NotificationSubscription.cs +++ b/save-points/08-templated-components/BlazingPizza.Shared/NotificationSubscription.cs @@ -2,13 +2,13 @@ public class NotificationSubscription { - public int NotificationSubscriptionId { get; set; } + public int? NotificationSubscriptionId { get; set; } - public string UserId { get; set; } + public string? UserId { get; set; } - public string Url { get; set; } + public string? Url { get; set; } - public string P256dh { get; set; } + public string? P256dh { get; set; } - public string Auth { get; set; } -} + public string? Auth { get; set; } +} \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.Shared/Order.cs b/save-points/08-templated-components/BlazingPizza.Shared/Order.cs index 180ec55b..f1211588 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/Order.cs +++ b/save-points/08-templated-components/BlazingPizza.Shared/Order.cs @@ -6,13 +6,15 @@ public class Order { public int OrderId { get; set; } - public string UserId { get; set; } + // Set by the server during POST + public string? UserId { get; set; } public DateTime CreatedTime { get; set; } public Address DeliveryAddress { get; set; } = new Address(); - public LatLong DeliveryLocation { get; set; } + // Set by server during POST + public LatLong? DeliveryLocation { get; set; } public List Pizzas { get; set; } = new List(); diff --git a/save-points/08-templated-components/BlazingPizza.Shared/OrderWithStatus.cs b/save-points/08-templated-components/BlazingPizza.Shared/OrderWithStatus.cs index fbfac7af..c0e3495a 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/OrderWithStatus.cs +++ b/save-points/08-templated-components/BlazingPizza.Shared/OrderWithStatus.cs @@ -9,16 +9,19 @@ 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; } + // Set from DB + public Order Order { get; set; } = null!; - public string StatusText { get; set; } + // Set from Order + public string StatusText { get; set; } = null!; public bool IsDelivered => StatusText == "Delivered"; - public List MapMarkers { get; set; } + public List MapMarkers { get; set; } = null!; public static OrderWithStatus FromOrder(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // To simulate a real backend process, we fake status updates based on the amount // of time since the order was placed string statusText; @@ -65,6 +68,7 @@ public static OrderWithStatus FromOrder(Order order) private static LatLong ComputeStartPosition(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // Random but deterministic based on order ID var rng = new Random(order.OrderId); var distance = 0.01 + rng.NextDouble() * 0.02; diff --git a/save-points/08-templated-components/BlazingPizza.Shared/Pizza.cs b/save-points/08-templated-components/BlazingPizza.Shared/Pizza.cs index 2a7a1c29..150de954 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/Pizza.cs +++ b/save-points/08-templated-components/BlazingPizza.Shared/Pizza.cs @@ -15,22 +15,24 @@ public class Pizza public int OrderId { get; set; } - public PizzaSpecial Special { get; set; } + public PizzaSpecial? Special { get; set; } public int SpecialId { get; set; } public int Size { get; set; } - public List Toppings { get; set; } + public List Toppings { get; set; } = new(); public decimal GetBasePrice() { + if(Special == null) throw new NullReferenceException($"{nameof(Special)} was null when calculating Base Price."); return ((decimal)Size / (decimal)DefaultSize) * Special.BasePrice; } public decimal GetTotalPrice() { - return GetBasePrice() + Toppings.Sum(t => t.Topping.Price); + if (Toppings.Any(t => t.Topping is null)) throw new NullReferenceException($"{nameof(Toppings)} contained null when calculating the Total Price."); + return GetBasePrice() + Toppings.Sum(t => t.Topping!.Price); } public string GetFormattedTotalPrice() diff --git a/save-points/08-templated-components/BlazingPizza.Shared/PizzaSpecial.cs b/save-points/08-templated-components/BlazingPizza.Shared/PizzaSpecial.cs index 1fd9006a..d53ab286 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/PizzaSpecial.cs +++ b/save-points/08-templated-components/BlazingPizza.Shared/PizzaSpecial.cs @@ -7,13 +7,13 @@ public class PizzaSpecial { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal BasePrice { get; set; } - public string Description { get; set; } + public string Description { get; set; } = string.Empty; - public string ImageUrl { get; set; } + public string ImageUrl { get; set; } = "img/pizzas/cheese.jpg"; public string GetFormattedBasePrice() => BasePrice.ToString("0.00"); } \ No newline at end of file diff --git a/save-points/08-templated-components/BlazingPizza.Shared/PizzaTopping.cs b/save-points/08-templated-components/BlazingPizza.Shared/PizzaTopping.cs index 1c062732..724f1c06 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/PizzaTopping.cs +++ b/save-points/08-templated-components/BlazingPizza.Shared/PizzaTopping.cs @@ -2,7 +2,7 @@ public class PizzaTopping { - public Topping Topping { get; set; } + public Topping? Topping { get; set; } public int ToppingId { get; set; } diff --git a/save-points/08-templated-components/BlazingPizza.Shared/Topping.cs b/save-points/08-templated-components/BlazingPizza.Shared/Topping.cs index 4eb67ae9..29b9e790 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/Topping.cs +++ b/save-points/08-templated-components/BlazingPizza.Shared/Topping.cs @@ -4,7 +4,7 @@ public class Topping { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal Price { get; set; } diff --git a/save-points/08-templated-components/BlazingPizza.Shared/UserInfo.cs b/save-points/08-templated-components/BlazingPizza.Shared/UserInfo.cs index babb4315..fb0045bf 100644 --- a/save-points/08-templated-components/BlazingPizza.Shared/UserInfo.cs +++ b/save-points/08-templated-components/BlazingPizza.Shared/UserInfo.cs @@ -4,6 +4,6 @@ public class UserInfo { public bool IsAuthenticated { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; } \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingComponents/BlazingComponents.csproj b/save-points/09-progressive-web-app/BlazingComponents/BlazingComponents.csproj index 89335194..9864380a 100644 --- a/save-points/09-progressive-web-app/BlazingComponents/BlazingComponents.csproj +++ b/save-points/09-progressive-web-app/BlazingComponents/BlazingComponents.csproj @@ -2,7 +2,7 @@ $(TargetFrameworkVersion) - 3.0 + enable diff --git a/save-points/09-progressive-web-app/BlazingComponents/TemplatedDialog.razor b/save-points/09-progressive-web-app/BlazingComponents/TemplatedDialog.razor index 52d3dd95..be590222 100644 --- a/save-points/09-progressive-web-app/BlazingComponents/TemplatedDialog.razor +++ b/save-points/09-progressive-web-app/BlazingComponents/TemplatedDialog.razor @@ -8,6 +8,6 @@ } @code { - [Parameter] public RenderFragment ChildContent { get; set; } + [Parameter, EditorRequired] public RenderFragment? ChildContent { get; set; } [Parameter] public bool Show { get; set; } } diff --git a/save-points/09-progressive-web-app/BlazingComponents/TemplatedList.razor b/save-points/09-progressive-web-app/BlazingComponents/TemplatedList.razor index 7961600e..c75e411f 100644 --- a/save-points/09-progressive-web-app/BlazingComponents/TemplatedList.razor +++ b/save-points/09-progressive-web-app/BlazingComponents/TemplatedList.razor @@ -1,6 +1,6 @@ @typeparam TItem -@if (items == null) +@if (items is null) { @Loading } @@ -14,23 +14,29 @@ else @foreach (var item in items) {
    - @Item(item) + @if (Item is not null) + { + @Item(item) + }
    }
    } @code { - IEnumerable items; + IEnumerable? items; - [Parameter] public Func>> Loader { get; set; } - [Parameter] public RenderFragment Loading { get; set; } - [Parameter] public RenderFragment Empty { get; set; } - [Parameter] public RenderFragment Item { get; set; } - [Parameter] public string ListGroupClass { get; set; } + [Parameter, EditorRequired] public Func>>? Loader { get; set; } + [Parameter] public RenderFragment? Loading { get; set; } + [Parameter] public RenderFragment? Empty { get; set; } + [Parameter, EditorRequired] public RenderFragment? Item { 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/save-points/09-progressive-web-app/BlazingPizza.Client/App.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/App.razor index 815659c4..5b6fc0fa 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/App.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/App.razor @@ -1,6 +1,6 @@  - - + + @@ -9,11 +9,11 @@
    Please wait...
    -
    - - -
    Sorry, there's nothing at this address.
    -
    -
    -
    +
    + + +
    Sorry, there's nothing at this address.
    +
    +
    +
    diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/BlazingPizza.Client.csproj b/save-points/09-progressive-web-app/BlazingPizza.Client/BlazingPizza.Client.csproj index 3573ae56..79dc4606 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/BlazingPizza.Client.csproj +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/BlazingPizza.Client.csproj @@ -1,21 +1,23 @@  - - $(TargetFrameworkVersion) - enable - + + $(TargetFrameworkVersion) + true + enable + - - - - - - + + + + + + + - + - - - + + + diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/OrderState.cs b/save-points/09-progressive-web-app/BlazingPizza.Client/OrderState.cs index 00dd3627..991a7512 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/OrderState.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/OrderState.cs @@ -4,7 +4,7 @@ public class OrderState { public bool ShowingConfigureDialog { get; private set; } - public Pizza ConfiguringPizza { get; private set; } + public Pizza? ConfiguringPizza { get; private set; } public Order Order { get; private set; } = new Order(); @@ -29,8 +29,11 @@ public void CancelConfigurePizzaDialog() public void ConfirmConfigurePizzaDialog() { - Order.Pizzas.Add(ConfiguringPizza); - ConfiguringPizza = null; + if (ConfiguringPizza is not null) + { + Order.Pizzas.Add(ConfiguringPizza); + ConfiguringPizza = null; + } ShowingConfigureDialog = false; } diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/OrdersClient.cs b/save-points/09-progressive-web-app/BlazingPizza.Client/OrdersClient.cs index c2c1a398..a24fc52d 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/OrdersClient.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/OrdersClient.cs @@ -12,11 +12,11 @@ public OrdersClient(HttpClient httpClient) } public async Task> GetOrders() => - await httpClient.GetFromJsonAsync("orders", OrderContext.Default.ListOrderWithStatus); + await httpClient.GetFromJsonAsync("orders", OrderContext.Default.ListOrderWithStatus) ?? new(); public async Task GetOrder(int orderId) => - await httpClient.GetFromJsonAsync($"orders/{orderId}", OrderContext.Default.OrderWithStatus); + await httpClient.GetFromJsonAsync($"orders/{orderId}", OrderContext.Default.OrderWithStatus) ?? new(); public async Task PlaceOrder(Order order) diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Authentication.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Authentication.razor index 5691b4b0..c7266152 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Authentication.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Authentication.razor @@ -1,6 +1,5 @@ @page "/authentication/{action}" @inject OrderState OrderState -@inject NavigationManager NavigationManager @code{ - [Parameter] public string Action { get; set; } + [Parameter] public string Action { get; set; } = string.Empty; public PizzaAuthenticationState RemoteAuthenticationState { get; set; } = new PizzaAuthenticationState(); @@ -24,9 +23,9 @@ private void RestorePizza(PizzaAuthenticationState pizzaState) { - if (pizzaState.Order != null) + if (pizzaState.Order is not null) { OrderState.ReplaceOrder(pizzaState.Order); } } -} \ No newline at end of file +} diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Checkout.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Checkout.razor index c78cacd1..53e45fe9 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Checkout.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Checkout.razor @@ -41,7 +41,7 @@ async Task RequestNotificationSubscriptionAsync() { var subscription = await JSRuntime.InvokeAsync("blazorPushNotifications.requestSubscription"); - if (subscription != null) + if (subscription is not null) { try { diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Index.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Index.razor index ca5ac084..04b8bce6 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Index.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/Index.razor @@ -1,12 +1,11 @@ @page "/" @inject HttpClient HttpClient @inject OrderState OrderState -@inject NavigationManager NavigationManager @inject IJSRuntime JS
      - @if (specials != null) + @if (specials is not null) { @foreach (var special in specials) { @@ -56,7 +55,7 @@ @code { - List specials; + List? specials; Order Order => OrderState.Order; protected override async Task OnInitializedAsync() @@ -66,7 +65,7 @@ async Task RemovePizza(Pizza configuredPizza) { - if (await JS.Confirm($"Remove {configuredPizza.Special.Name} pizza from the order?")) + if (await JS.Confirm($"Remove {configuredPizza.Special?.Name} pizza from the order?")) { OrderState.RemoveConfiguredPizza(configuredPizza); } diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/MyOrders.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/MyOrders.razor index ad99fb4f..1efeb194 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/MyOrders.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/MyOrders.razor @@ -45,4 +45,4 @@ } return ordersWithStatus; } -} +} \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/OrderDetails.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/OrderDetails.razor index b9500427..79c18741 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/OrderDetails.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Pages/OrderDetails.razor @@ -12,7 +12,7 @@

      Nope

      Sorry, this order could not be loaded.

      } - else if (orderWithStatus == null) + else if (orderWithStatus is null) { Loading... } @@ -42,9 +42,9 @@ @code { [Parameter] public int OrderId { get; set; } - OrderWithStatus orderWithStatus; + OrderWithStatus? orderWithStatus; bool invalidOrder; - CancellationTokenSource pollingCancellationToken; + CancellationTokenSource? pollingCancellationToken; protected override void OnParametersSet() { diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/PizzaAuthenticationState.cs b/save-points/09-progressive-web-app/BlazingPizza.Client/PizzaAuthenticationState.cs index 78627625..589d0620 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/PizzaAuthenticationState.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/PizzaAuthenticationState.cs @@ -4,5 +4,5 @@ namespace BlazingPizza.Client; public class PizzaAuthenticationState : RemoteAuthenticationState { - public Order Order { get; set; } + public Order? Order { get; set; } } diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/AddressEditor.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/AddressEditor.razor index 05e76410..fb36ff3a 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/AddressEditor.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/AddressEditor.razor @@ -47,5 +47,5 @@
    @code { - [Parameter] public Address Address { get; set; } -} + [Parameter, EditorRequired] public Address Address { get; set; } = new(); +} \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor index 67096b01..47b708c6 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor @@ -1,76 +1,82 @@ @inject HttpClient HttpClient -
    -

    @Pizza.Special.Name

    - @Pizza.Special.Description -
    -
    -
    - - - - @(Pizza.Size)" (£@(Pizza.GetFormattedTotalPrice())) - -
    -
    - - @if (toppings == null) - { - - } - else if (Pizza.Toppings.Count >= 6) - { -
    (maximum reached)
    - } - else - { - + + @(Pizza.Size)" (£@(Pizza.GetFormattedTotalPrice())) + +
    +
    + + @if (toppings is null) { - + } - - } -
    + else if (Pizza.Toppings.Count >= 6) + { +
    (maximum reached)
    + } + else + { + + } +
    -
    - @foreach (var topping in Pizza.Toppings) - { -
    - @topping.Topping.Name - @topping.Topping.GetFormattedPrice() - +
    + @foreach (var topping in Pizza.Toppings) + { + if (topping?.Topping is not null) + { +
    + @topping.Topping.Name + @topping.Topping.GetFormattedPrice() + +
    + } + }
    - } -
    - + + +
    + + + Price: @(Pizza.GetFormattedTotalPrice()) + + +
    -
    - - - Price: @(Pizza.GetFormattedTotalPrice()) - - -
    @code { - List toppings; + List? toppings; - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnCancel { get; set; } - [Parameter] public EventCallback OnConfirm { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnCancel { get; set; } + [Parameter, EditorRequired] public EventCallback OnConfirm { get; set; } protected async override Task OnInitializedAsync() { - toppings = await HttpClient.GetFromJsonAsync>("toppings"); + toppings = await HttpClient.GetFromJsonAsync>("toppings") ?? new(); } void ToppingSelected(ChangeEventArgs e) { - if (int.TryParse((string)e.Value, out var index) && index >= 0) + if (toppings is null) return; + if (int.TryParse((string?)e.Value, out var index) && index >= 0) { AddTopping(toppings[index]); } @@ -78,7 +84,7 @@ void AddTopping(Topping topping) { - if (Pizza.Toppings.Find(pt => pt.Topping == topping) == null) + if (Pizza.Toppings.Find(pt => pt.Topping == topping) is null) { Pizza.Toppings.Add(new PizzaTopping() { Topping = topping }); } diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor index e0e9f41d..f7f7a31d 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor @@ -1,10 +1,10 @@ 
    x -
    @(Pizza.Size)" @Pizza.Special.Name
    +
    @(Pizza.Size)" @Pizza.Special?.Name
      @foreach (var topping in Pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    @@ -13,6 +13,6 @@
    @code { - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnRemoved { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnRemoved { get; set; } } \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/LoginDisplay.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/LoginDisplay.razor index 4d483eb9..b5c5bfa7 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/LoginDisplay.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/LoginDisplay.razor @@ -1,5 +1,4 @@ @inject NavigationManager Navigation -@inject SignOutSessionStateManager SignOutManager -@code{ - async Task BeginSignOut() +@code { + void BeginSignOut() { - await SignOutManager.SetSignOutState(); - Navigation.NavigateTo("authentication/logout"); + Navigation.NavigateToLogout("authentication/logout"); } } \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/OrderReview.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/OrderReview.razor index e1424934..56668721 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/OrderReview.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/OrderReview.razor @@ -3,7 +3,7 @@

    @(pizza.Size)" - @pizza.Special.Name + @pizza.Special?.Name (£@pizza.GetFormattedTotalPrice())

    @@ -11,7 +11,7 @@
      @foreach (var topping in pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    } @@ -24,5 +24,5 @@

    @code { - [Parameter] public Order Order { get; set; } + [Parameter, EditorRequired] public Order Order { get; set; } = new(); } diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/RedirectToLogin.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/RedirectToLogin.razor index 16c4bea2..696c387d 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/RedirectToLogin.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/Shared/RedirectToLogin.razor @@ -2,6 +2,6 @@ @code { protected override void OnInitialized() { - Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}"); + Navigation.NavigateToLogin($"authentication/login"); } } \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Client/_Imports.razor b/save-points/09-progressive-web-app/BlazingPizza.Client/_Imports.razor index b575d7f7..af8847ae 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Client/_Imports.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.Client/_Imports.razor @@ -12,4 +12,4 @@ @using BlazingPizza.Client.Shared @using BlazingPizza.ComponentsLibrary @using BlazingPizza.ComponentsLibrary.Map -@using BlazingComponents +@using BlazingComponents \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj b/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj index edb7908d..a907e880 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj +++ b/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj @@ -2,7 +2,8 @@ $(TargetFrameworkVersion) - enable + true + enable @@ -15,4 +16,3 @@ - diff --git a/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Map.razor b/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Map.razor index c2413ac4..4325755e 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Map.razor +++ b/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Map.razor @@ -5,9 +5,9 @@ @code { string elementId = $"map-{Guid.NewGuid().ToString("D")}"; - + [Parameter] public double Zoom { get; set; } - [Parameter] public List Markers { get; set; } + [Parameter, EditorRequired] public List Markers { get; set; } = new(); protected async override Task OnAfterRenderAsync(bool firstRender) { diff --git a/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Marker.cs b/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Marker.cs index 63006067..d4dcdbad 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Marker.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Marker.cs @@ -1,13 +1,12 @@ -namespace BlazingPizza.ComponentsLibrary.Map +namespace BlazingPizza.ComponentsLibrary.Map; + +public class Marker { - public class Marker - { - public string Description { get; set; } + public string Description { get; set; } = string.Empty; - public double X { get; set; } + public double X { get; set; } - public double Y { get; set; } + public double Y { get; set; } - public bool ShowPopup { get; set; } - } -} + public bool ShowPopup { get; set; } +} \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Point.cs b/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Point.cs index 03841826..054e56ec 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Point.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.ComponentsLibrary/Map/Point.cs @@ -1,9 +1,9 @@ -namespace BlazingPizza.ComponentsLibrary.Map +namespace BlazingPizza.ComponentsLibrary.Map; + +public class Point { - public class Point - { - public double X { get; set; } + public double X { get; set; } - public double Y { get; set; } - } + public double Y { get; set; } } +// Compare this snippet from save-points\01-Components-and-layout\BlazingPizza.ComponentsLibrary\Map\Marker.cs: \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/save-points/09-progressive-web-app/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml index a1b61965..3f740c74 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml +++ b/save-points/09-progressive-web-app/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -17,7 +17,7 @@ {
    - @User.Identity.Name + @User?.Identity?.Name
    diff --git a/save-points/09-progressive-web-app/BlazingPizza.Server/BlazingPizza.Server.csproj b/save-points/09-progressive-web-app/BlazingPizza.Server/BlazingPizza.Server.csproj index abd2317b..b028c771 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Server/BlazingPizza.Server.csproj +++ b/save-points/09-progressive-web-app/BlazingPizza.Server/BlazingPizza.Server.csproj @@ -1,25 +1,26 @@  - - $(TargetFrameworkVersion) + + $(TargetFrameworkVersion) true - + enable + - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + diff --git a/save-points/09-progressive-web-app/BlazingPizza.Server/OrdersController.cs b/save-points/09-progressive-web-app/BlazingPizza.Server/OrdersController.cs index 3d2ba63f..adf19b41 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Server/OrdersController.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Server/OrdersController.cs @@ -23,7 +23,7 @@ public OrdersController(PizzaStoreContext db) public async Task>> GetOrders() { var orders = await _db.Orders - .Where(o => o.UserId == GetUserId()) + .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -38,7 +38,7 @@ public async Task> GetOrderWithStatus(int orderId) { var order = await _db.Orders .Where(o => o.OrderId == orderId) - .Where(o => o.UserId == GetUserId()) + .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -57,19 +57,19 @@ public async Task> PlaceOrder(Order order) { order.CreatedTime = DateTime.Now; order.DeliveryLocation = new LatLong(51.5001, -0.1239); - order.UserId = GetUserId(); + order.UserId = PizzaApiExtensions.GetUserId(HttpContext); // 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.SpecialId = pizza.Special?.Id ?? 0; pizza.Special = null; foreach (var topping in pizza.Toppings) { - topping.ToppingId = topping.Topping.Id; + topping.ToppingId = topping.Topping?.Id ?? 0; topping.Topping = null; } } @@ -78,7 +78,7 @@ public async Task> PlaceOrder(Order order) await _db.SaveChangesAsync(); // In the background, send push notifications if possible - var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); + var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == PizzaApiExtensions.GetUserId(HttpContext)).SingleOrDefaultAsync(); if (subscription != null) { _ = TrackAndSendNotificationsAsync(order, subscription); @@ -87,11 +87,6 @@ public async Task> PlaceOrder(Order order) return order.OrderId; } - private string GetUserId() - { - return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - } - private static async Task TrackAndSendNotificationsAsync(Order order, NotificationSubscription subscription) { // In a realistic case, some other backend process would track @@ -123,8 +118,8 @@ private static async Task SendNotificationAsync(Order order, NotificationSubscri await webPushClient.SendNotificationAsync(pushSubscription, payload, vapidDetails); } catch (Exception ex) - { + { Console.Error.WriteLine("Error sending push notification: " + ex.Message); } } -} +} \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Server/PizzaApiExtensions.cs b/save-points/09-progressive-web-app/BlazingPizza.Server/PizzaApiExtensions.cs index 524ca759..d277597f 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Server/PizzaApiExtensions.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Server/PizzaApiExtensions.cs @@ -19,6 +19,10 @@ public static WebApplication MapPizzaApi(this WebApplication app) // 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(context); + if (userId is null) + { + return Results.Unauthorized(); + } var oldSubscriptions = db.NotificationSubscriptions.Where(e => e.UserId == userId); db.NotificationSubscriptions.RemoveRange(oldSubscriptions); @@ -49,9 +53,6 @@ public static WebApplication MapPizzaApi(this WebApplication app) } - private static string GetUserId(HttpContext context) - { - return context.User.FindFirstValue(ClaimTypes.NameIdentifier); - } + public static string? GetUserId(HttpContext context) => context.User.FindFirstValue(ClaimTypes.NameIdentifier); } \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/Address.cs b/save-points/09-progressive-web-app/BlazingPizza.Shared/Address.cs index a671359a..15e546c1 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/Address.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/Address.cs @@ -5,20 +5,20 @@ public class Address public int Id { get; set; } [Required, MaxLength(100)] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; [Required, MaxLength(100)] - public string Line1 { get; set; } + public string Line1 { get; set; } = string.Empty; [MaxLength(100)] - public string Line2 { get; set; } + public string Line2 { get; set; } = string.Empty; [Required, MaxLength(50)] - public string City { get; set; } + public string City { get; set; } = string.Empty; [Required, MaxLength(20)] - public string Region { get; set; } + public string Region { get; set; } = string.Empty; [Required, MaxLength(20)] - public string PostalCode { get; set; } + public string PostalCode { get; set; } = string.Empty; } diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/BlazingPizza.Shared.csproj b/save-points/09-progressive-web-app/BlazingPizza.Shared/BlazingPizza.Shared.csproj index fb17e53b..785c204e 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/BlazingPizza.Shared.csproj +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/BlazingPizza.Shared.csproj @@ -1,17 +1,18 @@  - - $(TargetFrameworkVersion) - BlazingPizza + + $(TargetFrameworkVersion) + BlazingPizza true - + enable + - - - - + + + + diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/NotificationSubscription.cs b/save-points/09-progressive-web-app/BlazingPizza.Shared/NotificationSubscription.cs index 5c99e3f4..7f1ea4d9 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/NotificationSubscription.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/NotificationSubscription.cs @@ -2,13 +2,13 @@ public class NotificationSubscription { - public int NotificationSubscriptionId { get; set; } + public int? NotificationSubscriptionId { get; set; } - public string UserId { get; set; } + public string? UserId { get; set; } - public string Url { get; set; } + public string? Url { get; set; } - public string P256dh { get; set; } + public string? P256dh { get; set; } - public string Auth { get; set; } -} + public string? Auth { get; set; } +} \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/Order.cs b/save-points/09-progressive-web-app/BlazingPizza.Shared/Order.cs index 180ec55b..f1211588 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/Order.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/Order.cs @@ -6,13 +6,15 @@ public class Order { public int OrderId { get; set; } - public string UserId { get; set; } + // Set by the server during POST + public string? UserId { get; set; } public DateTime CreatedTime { get; set; } public Address DeliveryAddress { get; set; } = new Address(); - public LatLong DeliveryLocation { get; set; } + // Set by server during POST + public LatLong? DeliveryLocation { get; set; } public List Pizzas { get; set; } = new List(); diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/OrderWithStatus.cs b/save-points/09-progressive-web-app/BlazingPizza.Shared/OrderWithStatus.cs index fbfac7af..c0e3495a 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/OrderWithStatus.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/OrderWithStatus.cs @@ -9,16 +9,19 @@ 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; } + // Set from DB + public Order Order { get; set; } = null!; - public string StatusText { get; set; } + // Set from Order + public string StatusText { get; set; } = null!; public bool IsDelivered => StatusText == "Delivered"; - public List MapMarkers { get; set; } + public List MapMarkers { get; set; } = null!; public static OrderWithStatus FromOrder(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // To simulate a real backend process, we fake status updates based on the amount // of time since the order was placed string statusText; @@ -65,6 +68,7 @@ public static OrderWithStatus FromOrder(Order order) private static LatLong ComputeStartPosition(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // Random but deterministic based on order ID var rng = new Random(order.OrderId); var distance = 0.01 + rng.NextDouble() * 0.02; diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/Pizza.cs b/save-points/09-progressive-web-app/BlazingPizza.Shared/Pizza.cs index 2a7a1c29..150de954 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/Pizza.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/Pizza.cs @@ -15,22 +15,24 @@ public class Pizza public int OrderId { get; set; } - public PizzaSpecial Special { get; set; } + public PizzaSpecial? Special { get; set; } public int SpecialId { get; set; } public int Size { get; set; } - public List Toppings { get; set; } + public List Toppings { get; set; } = new(); public decimal GetBasePrice() { + if(Special == null) throw new NullReferenceException($"{nameof(Special)} was null when calculating Base Price."); return ((decimal)Size / (decimal)DefaultSize) * Special.BasePrice; } public decimal GetTotalPrice() { - return GetBasePrice() + Toppings.Sum(t => t.Topping.Price); + if (Toppings.Any(t => t.Topping is null)) throw new NullReferenceException($"{nameof(Toppings)} contained null when calculating the Total Price."); + return GetBasePrice() + Toppings.Sum(t => t.Topping!.Price); } public string GetFormattedTotalPrice() diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/PizzaSpecial.cs b/save-points/09-progressive-web-app/BlazingPizza.Shared/PizzaSpecial.cs index 1fd9006a..d53ab286 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/PizzaSpecial.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/PizzaSpecial.cs @@ -7,13 +7,13 @@ public class PizzaSpecial { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal BasePrice { get; set; } - public string Description { get; set; } + public string Description { get; set; } = string.Empty; - public string ImageUrl { get; set; } + public string ImageUrl { get; set; } = "img/pizzas/cheese.jpg"; public string GetFormattedBasePrice() => BasePrice.ToString("0.00"); } \ No newline at end of file diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/PizzaTopping.cs b/save-points/09-progressive-web-app/BlazingPizza.Shared/PizzaTopping.cs index 1c062732..724f1c06 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/PizzaTopping.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/PizzaTopping.cs @@ -2,7 +2,7 @@ public class PizzaTopping { - public Topping Topping { get; set; } + public Topping? Topping { get; set; } public int ToppingId { get; set; } diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/Topping.cs b/save-points/09-progressive-web-app/BlazingPizza.Shared/Topping.cs index 4eb67ae9..29b9e790 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/Topping.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/Topping.cs @@ -4,7 +4,7 @@ public class Topping { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal Price { get; set; } diff --git a/save-points/09-progressive-web-app/BlazingPizza.Shared/UserInfo.cs b/save-points/09-progressive-web-app/BlazingPizza.Shared/UserInfo.cs index babb4315..fb0045bf 100644 --- a/save-points/09-progressive-web-app/BlazingPizza.Shared/UserInfo.cs +++ b/save-points/09-progressive-web-app/BlazingPizza.Shared/UserInfo.cs @@ -4,6 +4,6 @@ public class UserInfo { public bool IsAuthenticated { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/BlazingComponents/BlazingComponents.csproj b/src/BlazingComponents/BlazingComponents.csproj index e0a4b76a..9864380a 100644 --- a/src/BlazingComponents/BlazingComponents.csproj +++ b/src/BlazingComponents/BlazingComponents.csproj @@ -2,6 +2,7 @@ $(TargetFrameworkVersion) + enable diff --git a/src/BlazingComponents/TemplatedDialog.razor b/src/BlazingComponents/TemplatedDialog.razor index 52d3dd95..be590222 100644 --- a/src/BlazingComponents/TemplatedDialog.razor +++ b/src/BlazingComponents/TemplatedDialog.razor @@ -8,6 +8,6 @@ } @code { - [Parameter] public RenderFragment ChildContent { get; set; } + [Parameter, EditorRequired] public RenderFragment? ChildContent { get; set; } [Parameter] public bool Show { get; set; } } diff --git a/src/BlazingComponents/TemplatedList.razor b/src/BlazingComponents/TemplatedList.razor index 7961600e..c75e411f 100644 --- a/src/BlazingComponents/TemplatedList.razor +++ b/src/BlazingComponents/TemplatedList.razor @@ -1,6 +1,6 @@ @typeparam TItem -@if (items == null) +@if (items is null) { @Loading } @@ -14,23 +14,29 @@ else @foreach (var item in items) {
    - @Item(item) + @if (Item is not null) + { + @Item(item) + }
    }
    } @code { - IEnumerable items; + IEnumerable? items; - [Parameter] public Func>> Loader { get; set; } - [Parameter] public RenderFragment Loading { get; set; } - [Parameter] public RenderFragment Empty { get; set; } - [Parameter] public RenderFragment Item { get; set; } - [Parameter] public string ListGroupClass { get; set; } + [Parameter, EditorRequired] public Func>>? Loader { get; set; } + [Parameter] public RenderFragment? Loading { get; set; } + [Parameter] public RenderFragment? Empty { get; set; } + [Parameter, EditorRequired] public RenderFragment? Item { 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/App.razor b/src/BlazingPizza.Client/App.razor index 21d5a455..5b6fc0fa 100644 --- a/src/BlazingPizza.Client/App.razor +++ b/src/BlazingPizza.Client/App.razor @@ -1,6 +1,6 @@  - - + + @@ -9,11 +9,11 @@
    Please wait...
    -
    - - -
    Sorry, there's nothing at this address.
    -
    -
    -
    +
    + + +
    Sorry, there's nothing at this address.
    +
    +
    +
    diff --git a/src/BlazingPizza.Client/BlazingPizza.Client.csproj b/src/BlazingPizza.Client/BlazingPizza.Client.csproj index 44802d79..79dc4606 100644 --- a/src/BlazingPizza.Client/BlazingPizza.Client.csproj +++ b/src/BlazingPizza.Client/BlazingPizza.Client.csproj @@ -1,22 +1,23 @@  - - $(TargetFrameworkVersion) + + $(TargetFrameworkVersion) true - + enable + - - + + - + - - + + - + - - - + + + diff --git a/src/BlazingPizza.Client/OrderState.cs b/src/BlazingPizza.Client/OrderState.cs index 00dd3627..991a7512 100644 --- a/src/BlazingPizza.Client/OrderState.cs +++ b/src/BlazingPizza.Client/OrderState.cs @@ -4,7 +4,7 @@ public class OrderState { public bool ShowingConfigureDialog { get; private set; } - public Pizza ConfiguringPizza { get; private set; } + public Pizza? ConfiguringPizza { get; private set; } public Order Order { get; private set; } = new Order(); @@ -29,8 +29,11 @@ public void CancelConfigurePizzaDialog() public void ConfirmConfigurePizzaDialog() { - Order.Pizzas.Add(ConfiguringPizza); - ConfiguringPizza = null; + if (ConfiguringPizza is not null) + { + Order.Pizzas.Add(ConfiguringPizza); + ConfiguringPizza = null; + } ShowingConfigureDialog = false; } diff --git a/src/BlazingPizza.Client/OrdersClient.cs b/src/BlazingPizza.Client/OrdersClient.cs index b4a08f1e..a24fc52d 100644 --- a/src/BlazingPizza.Client/OrdersClient.cs +++ b/src/BlazingPizza.Client/OrdersClient.cs @@ -1,5 +1,4 @@ using System.Net.Http.Json; -using System.Text.Json; namespace BlazingPizza.Client; @@ -13,11 +12,11 @@ public OrdersClient(HttpClient httpClient) } public async Task> GetOrders() => - await httpClient.GetFromJsonAsync("orders", OrderContext.Default.ListOrderWithStatus); + await httpClient.GetFromJsonAsync("orders", OrderContext.Default.ListOrderWithStatus) ?? new(); public async Task GetOrder(int orderId) => - await httpClient.GetFromJsonAsync($"orders/{orderId}", OrderContext.Default.OrderWithStatus); + await httpClient.GetFromJsonAsync($"orders/{orderId}", OrderContext.Default.OrderWithStatus) ?? new(); public async Task PlaceOrder(Order order) diff --git a/src/BlazingPizza.Client/Pages/Authentication.razor b/src/BlazingPizza.Client/Pages/Authentication.razor index abed0b1f..7fbca1da 100644 --- a/src/BlazingPizza.Client/Pages/Authentication.razor +++ b/src/BlazingPizza.Client/Pages/Authentication.razor @@ -9,7 +9,7 @@ Action="@Action" /> @code{ - [Parameter] public string Action { get; set; } + [Parameter] public string Action { get; set; } = string.Empty; public PizzaAuthenticationState RemoteAuthenticationState { get; set; } = new PizzaAuthenticationState(); @@ -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 c78cacd1..53e45fe9 100644 --- a/src/BlazingPizza.Client/Pages/Checkout.razor +++ b/src/BlazingPizza.Client/Pages/Checkout.razor @@ -41,7 +41,7 @@ async Task RequestNotificationSubscriptionAsync() { var subscription = await JSRuntime.InvokeAsync("blazorPushNotifications.requestSubscription"); - if (subscription != null) + if (subscription is not null) { try { diff --git a/src/BlazingPizza.Client/Pages/Index.razor b/src/BlazingPizza.Client/Pages/Index.razor index f8ff79df..43dde123 100644 --- a/src/BlazingPizza.Client/Pages/Index.razor +++ b/src/BlazingPizza.Client/Pages/Index.razor @@ -6,7 +6,7 @@
      - @if (specials != null) + @if (specials is not null) { @foreach (var special in specials) { @@ -56,17 +56,17 @@ @code { - List specials; + List? specials; Order Order => OrderState.Order; protected override async Task OnInitializedAsync() { - specials = await HttpClient.GetFromJsonAsync("specials", BlazingPizza.OrderContext.Default.ListPizzaSpecial); + specials = await HttpClient.GetFromJsonAsync>("specials"); } async Task RemovePizza(Pizza configuredPizza) { - if (await JS.Confirm($"Remove {configuredPizza.Special.Name} pizza from the order?")) + if (await JS.Confirm($"Remove {configuredPizza.Special?.Name} pizza from the order?")) { OrderState.RemoveConfiguredPizza(configuredPizza); } diff --git a/src/BlazingPizza.Client/Pages/MyOrders.razor b/src/BlazingPizza.Client/Pages/MyOrders.razor index ad99fb4f..1efeb194 100644 --- a/src/BlazingPizza.Client/Pages/MyOrders.razor +++ b/src/BlazingPizza.Client/Pages/MyOrders.razor @@ -45,4 +45,4 @@ } return ordersWithStatus; } -} +} \ No newline at end of file diff --git a/src/BlazingPizza.Client/Pages/OrderDetails.razor b/src/BlazingPizza.Client/Pages/OrderDetails.razor index b9500427..79c18741 100644 --- a/src/BlazingPizza.Client/Pages/OrderDetails.razor +++ b/src/BlazingPizza.Client/Pages/OrderDetails.razor @@ -12,7 +12,7 @@

      Nope

      Sorry, this order could not be loaded.

      } - else if (orderWithStatus == null) + else if (orderWithStatus is null) { Loading... } @@ -42,9 +42,9 @@ @code { [Parameter] public int OrderId { get; set; } - OrderWithStatus orderWithStatus; + OrderWithStatus? orderWithStatus; bool invalidOrder; - CancellationTokenSource pollingCancellationToken; + CancellationTokenSource? pollingCancellationToken; protected override void OnParametersSet() { diff --git a/src/BlazingPizza.Client/PizzaAuthenticationState.cs b/src/BlazingPizza.Client/PizzaAuthenticationState.cs index 78627625..589d0620 100644 --- a/src/BlazingPizza.Client/PizzaAuthenticationState.cs +++ b/src/BlazingPizza.Client/PizzaAuthenticationState.cs @@ -4,5 +4,5 @@ namespace BlazingPizza.Client; public class PizzaAuthenticationState : RemoteAuthenticationState { - public Order Order { get; set; } + public Order? Order { get; set; } } diff --git a/src/BlazingPizza.Client/Shared/AddressEditor.razor b/src/BlazingPizza.Client/Shared/AddressEditor.razor index 05e76410..fb36ff3a 100644 --- a/src/BlazingPizza.Client/Shared/AddressEditor.razor +++ b/src/BlazingPizza.Client/Shared/AddressEditor.razor @@ -47,5 +47,5 @@
    @code { - [Parameter] public Address Address { get; set; } -} + [Parameter, EditorRequired] public Address Address { get; set; } = new(); +} \ No newline at end of file diff --git a/src/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor b/src/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor index f10c7990..47b708c6 100644 --- a/src/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor +++ b/src/BlazingPizza.Client/Shared/ConfigurePizzaDialog.razor @@ -1,76 +1,82 @@ @inject HttpClient HttpClient -
    -

    @Pizza.Special.Name

    - @Pizza.Special.Description -
    -
    -
    - - - - @(Pizza.Size)" (£@(Pizza.GetFormattedTotalPrice())) - -
    -
    - - @if (toppings == null) - { - - } - else if (Pizza.Toppings.Count >= 6) - { -
    (maximum reached)
    - } - else - { - + + @(Pizza.Size)" (£@(Pizza.GetFormattedTotalPrice())) + +
    +
    + + @if (toppings is null) { - + } - - } -
    + else if (Pizza.Toppings.Count >= 6) + { +
    (maximum reached)
    + } + else + { + + } +
    -
    - @foreach (var topping in Pizza.Toppings) - { -
    - @topping.Topping.Name - @topping.Topping.GetFormattedPrice() - +
    + @foreach (var topping in Pizza.Toppings) + { + if (topping?.Topping is not null) + { +
    + @topping.Topping.Name + @topping.Topping.GetFormattedPrice() + +
    + } + }
    - } -
    - + + +
    + + + Price: @(Pizza.GetFormattedTotalPrice()) + + +
    -
    - - - Price: @(Pizza.GetFormattedTotalPrice()) - - -
    @code { - List toppings; + List? toppings; - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnCancel { get; set; } - [Parameter] public EventCallback OnConfirm { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnCancel { get; set; } + [Parameter, EditorRequired] public EventCallback OnConfirm { get; set; } protected async override Task OnInitializedAsync() { - toppings = await HttpClient.GetFromJsonAsync("toppings", BlazingPizza.OrderContext.Default.ListTopping); + toppings = await HttpClient.GetFromJsonAsync>("toppings") ?? new(); } void ToppingSelected(ChangeEventArgs e) { - if (int.TryParse((string)e.Value, out var index) && index >= 0) + if (toppings is null) return; + if (int.TryParse((string?)e.Value, out var index) && index >= 0) { AddTopping(toppings[index]); } @@ -78,7 +84,7 @@ void AddTopping(Topping topping) { - if (Pizza.Toppings.Find(pt => pt.Topping == topping) == null) + if (Pizza.Toppings.Find(pt => pt.Topping == topping) is null) { Pizza.Toppings.Add(new PizzaTopping() { Topping = topping }); } diff --git a/src/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor b/src/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor index e0e9f41d..f7f7a31d 100644 --- a/src/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor +++ b/src/BlazingPizza.Client/Shared/ConfiguredPizzaItem.razor @@ -1,10 +1,10 @@ 
    x -
    @(Pizza.Size)" @Pizza.Special.Name
    +
    @(Pizza.Size)" @Pizza.Special?.Name
      @foreach (var topping in Pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    @@ -13,6 +13,6 @@
    @code { - [Parameter] public Pizza Pizza { get; set; } - [Parameter] public EventCallback OnRemoved { get; set; } + [Parameter, EditorRequired] public Pizza Pizza { get; set; } = new(); + [Parameter, EditorRequired] public EventCallback OnRemoved { get; set; } } \ No newline at end of file diff --git a/src/BlazingPizza.Client/Shared/LoginDisplay.razor b/src/BlazingPizza.Client/Shared/LoginDisplay.razor index 4d483eb9..b5c5bfa7 100644 --- a/src/BlazingPizza.Client/Shared/LoginDisplay.razor +++ b/src/BlazingPizza.Client/Shared/LoginDisplay.razor @@ -1,5 +1,4 @@ @inject NavigationManager Navigation -@inject SignOutSessionStateManager SignOutManager -@code{ - async Task BeginSignOut() +@code { + void BeginSignOut() { - await SignOutManager.SetSignOutState(); - Navigation.NavigateTo("authentication/logout"); + Navigation.NavigateToLogout("authentication/logout"); } } \ No newline at end of file diff --git a/src/BlazingPizza.Client/Shared/OrderReview.razor b/src/BlazingPizza.Client/Shared/OrderReview.razor index e1424934..56668721 100644 --- a/src/BlazingPizza.Client/Shared/OrderReview.razor +++ b/src/BlazingPizza.Client/Shared/OrderReview.razor @@ -3,7 +3,7 @@

    @(pizza.Size)" - @pizza.Special.Name + @pizza.Special?.Name (£@pizza.GetFormattedTotalPrice())

    @@ -11,7 +11,7 @@
      @foreach (var topping in pizza.Toppings) { -
    • + @topping.Topping.Name
    • +
    • + @topping.Topping?.Name
    • }
    } @@ -24,5 +24,5 @@

    @code { - [Parameter] public Order Order { get; set; } + [Parameter, EditorRequired] public Order Order { get; set; } = new(); } diff --git a/src/BlazingPizza.Client/Shared/RedirectToLogin.razor b/src/BlazingPizza.Client/Shared/RedirectToLogin.razor index 16c4bea2..696c387d 100644 --- a/src/BlazingPizza.Client/Shared/RedirectToLogin.razor +++ b/src/BlazingPizza.Client/Shared/RedirectToLogin.razor @@ -2,6 +2,6 @@ @code { protected override void OnInitialized() { - Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}"); + Navigation.NavigateToLogin($"authentication/login"); } } \ No newline at end of file diff --git a/src/BlazingPizza.Client/_Imports.razor b/src/BlazingPizza.Client/_Imports.razor index b575d7f7..af8847ae 100644 --- a/src/BlazingPizza.Client/_Imports.razor +++ b/src/BlazingPizza.Client/_Imports.razor @@ -12,4 +12,4 @@ @using BlazingPizza.Client.Shared @using BlazingPizza.ComponentsLibrary @using BlazingPizza.ComponentsLibrary.Map -@using BlazingComponents +@using BlazingComponents \ No newline at end of file diff --git a/src/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj b/src/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj index cb996b1b..a907e880 100644 --- a/src/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj +++ b/src/BlazingPizza.ComponentsLibrary/BlazingPizza.ComponentsLibrary.csproj @@ -2,7 +2,8 @@ $(TargetFrameworkVersion) - true + true + enable diff --git a/src/BlazingPizza.ComponentsLibrary/Map/Map.razor b/src/BlazingPizza.ComponentsLibrary/Map/Map.razor index c2413ac4..4325755e 100644 --- a/src/BlazingPizza.ComponentsLibrary/Map/Map.razor +++ b/src/BlazingPizza.ComponentsLibrary/Map/Map.razor @@ -5,9 +5,9 @@ @code { string elementId = $"map-{Guid.NewGuid().ToString("D")}"; - + [Parameter] public double Zoom { get; set; } - [Parameter] public List Markers { get; set; } + [Parameter, EditorRequired] public List Markers { get; set; } = new(); protected async override Task OnAfterRenderAsync(bool firstRender) { diff --git a/src/BlazingPizza.ComponentsLibrary/Map/Marker.cs b/src/BlazingPizza.ComponentsLibrary/Map/Marker.cs index b067719c..d4dcdbad 100644 --- a/src/BlazingPizza.ComponentsLibrary/Map/Marker.cs +++ b/src/BlazingPizza.ComponentsLibrary/Map/Marker.cs @@ -2,11 +2,11 @@ public class Marker { - public string Description { get; set; } + public string Description { get; set; } = string.Empty; public double X { get; set; } public double Y { get; set; } public bool ShowPopup { get; set; } -} +} \ No newline at end of file diff --git a/src/BlazingPizza.ComponentsLibrary/Map/Point.cs b/src/BlazingPizza.ComponentsLibrary/Map/Point.cs index 34194163..054e56ec 100644 --- a/src/BlazingPizza.ComponentsLibrary/Map/Point.cs +++ b/src/BlazingPizza.ComponentsLibrary/Map/Point.cs @@ -5,4 +5,5 @@ public class Point public double X { get; set; } public double Y { get; set; } -} \ No newline at end of file +} +// Compare this snippet from save-points\01-Components-and-layout\BlazingPizza.ComponentsLibrary\Map\Marker.cs: \ No newline at end of file diff --git a/src/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/src/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml index a1b61965..3f740c74 100644 --- a/src/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml +++ b/src/BlazingPizza.Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -17,7 +17,7 @@ {
    - @User.Identity.Name + @User?.Identity?.Name
    diff --git a/src/BlazingPizza.Server/BlazingPizza.Server.csproj b/src/BlazingPizza.Server/BlazingPizza.Server.csproj index 339bbf85..b028c771 100644 --- a/src/BlazingPizza.Server/BlazingPizza.Server.csproj +++ b/src/BlazingPizza.Server/BlazingPizza.Server.csproj @@ -1,25 +1,26 @@  - - $(TargetFrameworkVersion) + + $(TargetFrameworkVersion) true - + enable + - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + diff --git a/src/BlazingPizza.Server/OrdersController.cs b/src/BlazingPizza.Server/OrdersController.cs index 3d2ba63f..adf19b41 100644 --- a/src/BlazingPizza.Server/OrdersController.cs +++ b/src/BlazingPizza.Server/OrdersController.cs @@ -23,7 +23,7 @@ public OrdersController(PizzaStoreContext db) public async Task>> GetOrders() { var orders = await _db.Orders - .Where(o => o.UserId == GetUserId()) + .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -38,7 +38,7 @@ public async Task> GetOrderWithStatus(int orderId) { var order = await _db.Orders .Where(o => o.OrderId == orderId) - .Where(o => o.UserId == GetUserId()) + .Where(o => o.UserId == PizzaApiExtensions.GetUserId(HttpContext)) .Include(o => o.DeliveryLocation) .Include(o => o.Pizzas).ThenInclude(p => p.Special) .Include(o => o.Pizzas).ThenInclude(p => p.Toppings).ThenInclude(t => t.Topping) @@ -57,19 +57,19 @@ public async Task> PlaceOrder(Order order) { order.CreatedTime = DateTime.Now; order.DeliveryLocation = new LatLong(51.5001, -0.1239); - order.UserId = GetUserId(); + order.UserId = PizzaApiExtensions.GetUserId(HttpContext); // 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.SpecialId = pizza.Special?.Id ?? 0; pizza.Special = null; foreach (var topping in pizza.Toppings) { - topping.ToppingId = topping.Topping.Id; + topping.ToppingId = topping.Topping?.Id ?? 0; topping.Topping = null; } } @@ -78,7 +78,7 @@ public async Task> PlaceOrder(Order order) await _db.SaveChangesAsync(); // In the background, send push notifications if possible - var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == GetUserId()).SingleOrDefaultAsync(); + var subscription = await _db.NotificationSubscriptions.Where(e => e.UserId == PizzaApiExtensions.GetUserId(HttpContext)).SingleOrDefaultAsync(); if (subscription != null) { _ = TrackAndSendNotificationsAsync(order, subscription); @@ -87,11 +87,6 @@ public async Task> PlaceOrder(Order order) return order.OrderId; } - private string GetUserId() - { - return HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); - } - private static async Task TrackAndSendNotificationsAsync(Order order, NotificationSubscription subscription) { // In a realistic case, some other backend process would track @@ -123,8 +118,8 @@ private static async Task SendNotificationAsync(Order order, NotificationSubscri await webPushClient.SendNotificationAsync(pushSubscription, payload, vapidDetails); } catch (Exception ex) - { + { Console.Error.WriteLine("Error sending push notification: " + ex.Message); } } -} +} \ No newline at end of file diff --git a/src/BlazingPizza.Server/PizzaApiExtensions.cs b/src/BlazingPizza.Server/PizzaApiExtensions.cs index 524ca759..d277597f 100644 --- a/src/BlazingPizza.Server/PizzaApiExtensions.cs +++ b/src/BlazingPizza.Server/PizzaApiExtensions.cs @@ -19,6 +19,10 @@ public static WebApplication MapPizzaApi(this WebApplication app) // 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(context); + if (userId is null) + { + return Results.Unauthorized(); + } var oldSubscriptions = db.NotificationSubscriptions.Where(e => e.UserId == userId); db.NotificationSubscriptions.RemoveRange(oldSubscriptions); @@ -49,9 +53,6 @@ public static WebApplication MapPizzaApi(this WebApplication app) } - private static string GetUserId(HttpContext context) - { - return context.User.FindFirstValue(ClaimTypes.NameIdentifier); - } + public static string? GetUserId(HttpContext context) => context.User.FindFirstValue(ClaimTypes.NameIdentifier); } \ No newline at end of file diff --git a/src/BlazingPizza.Server/Properties/launchSettings.json b/src/BlazingPizza.Server/Properties/launchSettings.json index c90e4fdb..f82b9563 100644 --- a/src/BlazingPizza.Server/Properties/launchSettings.json +++ b/src/BlazingPizza.Server/Properties/launchSettings.json @@ -11,19 +11,19 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" + } }, "BlazingPizza.Server": { "commandName": "Project", "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" + } } } } \ No newline at end of file diff --git a/src/BlazingPizza.Shared/Address.cs b/src/BlazingPizza.Shared/Address.cs index a671359a..15e546c1 100644 --- a/src/BlazingPizza.Shared/Address.cs +++ b/src/BlazingPizza.Shared/Address.cs @@ -5,20 +5,20 @@ public class Address public int Id { get; set; } [Required, MaxLength(100)] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; [Required, MaxLength(100)] - public string Line1 { get; set; } + public string Line1 { get; set; } = string.Empty; [MaxLength(100)] - public string Line2 { get; set; } + public string Line2 { get; set; } = string.Empty; [Required, MaxLength(50)] - public string City { get; set; } + public string City { get; set; } = string.Empty; [Required, MaxLength(20)] - public string Region { get; set; } + public string Region { get; set; } = string.Empty; [Required, MaxLength(20)] - public string PostalCode { get; set; } + public string PostalCode { get; set; } = string.Empty; } diff --git a/src/BlazingPizza.Shared/BlazingPizza.Shared.csproj b/src/BlazingPizza.Shared/BlazingPizza.Shared.csproj index fb17e53b..785c204e 100644 --- a/src/BlazingPizza.Shared/BlazingPizza.Shared.csproj +++ b/src/BlazingPizza.Shared/BlazingPizza.Shared.csproj @@ -1,17 +1,18 @@  - - $(TargetFrameworkVersion) - BlazingPizza + + $(TargetFrameworkVersion) + BlazingPizza true - + enable + - - - - + + + + diff --git a/src/BlazingPizza.Shared/NotificationSubscription.cs b/src/BlazingPizza.Shared/NotificationSubscription.cs index 5c99e3f4..7f1ea4d9 100644 --- a/src/BlazingPizza.Shared/NotificationSubscription.cs +++ b/src/BlazingPizza.Shared/NotificationSubscription.cs @@ -2,13 +2,13 @@ public class NotificationSubscription { - public int NotificationSubscriptionId { get; set; } + public int? NotificationSubscriptionId { get; set; } - public string UserId { get; set; } + public string? UserId { get; set; } - public string Url { get; set; } + public string? Url { get; set; } - public string P256dh { get; set; } + public string? P256dh { get; set; } - public string Auth { get; set; } -} + public string? Auth { get; set; } +} \ No newline at end of file diff --git a/src/BlazingPizza.Shared/Order.cs b/src/BlazingPizza.Shared/Order.cs index 180ec55b..f1211588 100644 --- a/src/BlazingPizza.Shared/Order.cs +++ b/src/BlazingPizza.Shared/Order.cs @@ -6,13 +6,15 @@ public class Order { public int OrderId { get; set; } - public string UserId { get; set; } + // Set by the server during POST + public string? UserId { get; set; } public DateTime CreatedTime { get; set; } public Address DeliveryAddress { get; set; } = new Address(); - public LatLong DeliveryLocation { get; set; } + // Set by server during POST + public LatLong? DeliveryLocation { get; set; } public List Pizzas { get; set; } = new List(); diff --git a/src/BlazingPizza.Shared/OrderWithStatus.cs b/src/BlazingPizza.Shared/OrderWithStatus.cs index fbfac7af..c0e3495a 100644 --- a/src/BlazingPizza.Shared/OrderWithStatus.cs +++ b/src/BlazingPizza.Shared/OrderWithStatus.cs @@ -9,16 +9,19 @@ 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; } + // Set from DB + public Order Order { get; set; } = null!; - public string StatusText { get; set; } + // Set from Order + public string StatusText { get; set; } = null!; public bool IsDelivered => StatusText == "Delivered"; - public List MapMarkers { get; set; } + public List MapMarkers { get; set; } = null!; public static OrderWithStatus FromOrder(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // To simulate a real backend process, we fake status updates based on the amount // of time since the order was placed string statusText; @@ -65,6 +68,7 @@ public static OrderWithStatus FromOrder(Order order) private static LatLong ComputeStartPosition(Order order) { + ArgumentNullException.ThrowIfNull(order.DeliveryLocation); // Random but deterministic based on order ID var rng = new Random(order.OrderId); var distance = 0.01 + rng.NextDouble() * 0.02; diff --git a/src/BlazingPizza.Shared/Pizza.cs b/src/BlazingPizza.Shared/Pizza.cs index 2a7a1c29..150de954 100644 --- a/src/BlazingPizza.Shared/Pizza.cs +++ b/src/BlazingPizza.Shared/Pizza.cs @@ -15,22 +15,24 @@ public class Pizza public int OrderId { get; set; } - public PizzaSpecial Special { get; set; } + public PizzaSpecial? Special { get; set; } public int SpecialId { get; set; } public int Size { get; set; } - public List Toppings { get; set; } + public List Toppings { get; set; } = new(); public decimal GetBasePrice() { + if(Special == null) throw new NullReferenceException($"{nameof(Special)} was null when calculating Base Price."); return ((decimal)Size / (decimal)DefaultSize) * Special.BasePrice; } public decimal GetTotalPrice() { - return GetBasePrice() + Toppings.Sum(t => t.Topping.Price); + if (Toppings.Any(t => t.Topping is null)) throw new NullReferenceException($"{nameof(Toppings)} contained null when calculating the Total Price."); + return GetBasePrice() + Toppings.Sum(t => t.Topping!.Price); } public string GetFormattedTotalPrice() diff --git a/src/BlazingPizza.Shared/PizzaSpecial.cs b/src/BlazingPizza.Shared/PizzaSpecial.cs index 1fd9006a..d53ab286 100644 --- a/src/BlazingPizza.Shared/PizzaSpecial.cs +++ b/src/BlazingPizza.Shared/PizzaSpecial.cs @@ -7,13 +7,13 @@ public class PizzaSpecial { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal BasePrice { get; set; } - public string Description { get; set; } + public string Description { get; set; } = string.Empty; - public string ImageUrl { get; set; } + public string ImageUrl { get; set; } = "img/pizzas/cheese.jpg"; public string GetFormattedBasePrice() => BasePrice.ToString("0.00"); } \ No newline at end of file diff --git a/src/BlazingPizza.Shared/PizzaTopping.cs b/src/BlazingPizza.Shared/PizzaTopping.cs index 1c062732..724f1c06 100644 --- a/src/BlazingPizza.Shared/PizzaTopping.cs +++ b/src/BlazingPizza.Shared/PizzaTopping.cs @@ -2,7 +2,7 @@ public class PizzaTopping { - public Topping Topping { get; set; } + public Topping? Topping { get; set; } public int ToppingId { get; set; } diff --git a/src/BlazingPizza.Shared/Topping.cs b/src/BlazingPizza.Shared/Topping.cs index 4eb67ae9..29b9e790 100644 --- a/src/BlazingPizza.Shared/Topping.cs +++ b/src/BlazingPizza.Shared/Topping.cs @@ -4,7 +4,7 @@ public class Topping { public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public decimal Price { get; set; } diff --git a/src/BlazingPizza.Shared/UserInfo.cs b/src/BlazingPizza.Shared/UserInfo.cs index babb4315..fb0045bf 100644 --- a/src/BlazingPizza.Shared/UserInfo.cs +++ b/src/BlazingPizza.Shared/UserInfo.cs @@ -4,6 +4,6 @@ public class UserInfo { public bool IsAuthenticated { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; } \ No newline at end of file