From 59c19c3869754d4ff6dc52c6a9d32c9bc2bcf92f Mon Sep 17 00:00:00 2001 From: Reuben Bond <203839+ReubenBond@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:03:58 -0700 Subject: [PATCH] Update Orleans App Service sample to work on App Service Linux (#10) * Update Orleans App Service sample to work on App Service Linux * Update Silo/Components/PurchasableProductTable.razor --------- Co-authored-by: David Pine --- .github/workflows/deploy.yml | 4 +- Abstractions/CartItem.cs | 1 + .../Orleans.ShoppingCart.Abstractions.csproj | 6 +- Abstractions/ProductCategory.cs | 1 + Abstractions/ProductDetails.cs | 20 ++- Grains/InventoryGrain.cs | 1 + Grains/Orleans.ShoppingCart.Grains.csproj | 8 +- Grains/ProductGrain.cs | 1 + Grains/ShoppingCartGrain.cs | 7 +- Silo/App.razor | 2 +- Silo/Components/ManageProductModal.razor | 2 +- Silo/Components/ProductTable.razor | 14 +- Silo/Components/PurchasableProductTable.razor | 27 ++-- Silo/Components/ShoppingCartSummary.razor | 2 +- Silo/Extensions/ProductDetailsExtensions.cs | 6 +- Silo/GlobalUsings.cs | 3 - Silo/Orleans.ShoppingCart.Silo.csproj | 16 +-- Silo/Pages/Cart.razor | 2 +- Silo/Pages/Index.razor | 2 +- Silo/Pages/Products.razor | 2 +- Silo/Pages/Products.razor.cs | 2 +- Silo/Pages/_Host.cshtml | 1 + Silo/Program.cs | 122 +++++++++++------- Silo/Shared/MainLayout.razor | 11 +- Silo/Shared/MainLayout.razor.cs | 6 +- Silo/Startup.cs | 45 ------- Silo/StartupTasks/SeedProductStoreTask.cs | 4 +- Silo/web.config | 12 -- global.json | 2 +- 29 files changed, 163 insertions(+), 169 deletions(-) delete mode 100644 Silo/Startup.cs delete mode 100644 Silo/web.config diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d9a11e0..9cb48c6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,10 +16,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup .NET 7.0 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: .NET publish shopping cart app run: dotnet publish ./Silo/Orleans.ShoppingCart.Silo.csproj --configuration Release diff --git a/Abstractions/CartItem.cs b/Abstractions/CartItem.cs index 3c71ba6..2399230 100644 --- a/Abstractions/CartItem.cs +++ b/Abstractions/CartItem.cs @@ -4,6 +4,7 @@ namespace Orleans.ShoppingCart.Abstractions; [GenerateSerializer, Immutable] +[Alias("Orleans.ShoppingCart.Abstractions.CartItem")] public sealed record class CartItem( string UserId, int Quantity, diff --git a/Abstractions/Orleans.ShoppingCart.Abstractions.csproj b/Abstractions/Orleans.ShoppingCart.Abstractions.csproj index 9004469..43ecb31 100644 --- a/Abstractions/Orleans.ShoppingCart.Abstractions.csproj +++ b/Abstractions/Orleans.ShoppingCart.Abstractions.csproj @@ -1,15 +1,15 @@ - net7.0 + net8.0 enable enable - + - + diff --git a/Abstractions/ProductCategory.cs b/Abstractions/ProductCategory.cs index b4df7fc..6fa335c 100644 --- a/Abstractions/ProductCategory.cs +++ b/Abstractions/ProductCategory.cs @@ -3,6 +3,7 @@ namespace Orleans.ShoppingCart.Abstractions; +[GenerateSerializer] public enum ProductCategory { Accessories, diff --git a/Abstractions/ProductDetails.cs b/Abstractions/ProductDetails.cs index b76112f..24be6c6 100644 --- a/Abstractions/ProductDetails.cs +++ b/Abstractions/ProductDetails.cs @@ -3,17 +3,25 @@ namespace Orleans.ShoppingCart.Abstractions; -[GenerateSerializer, Immutable] +[GenerateSerializer] public sealed record class ProductDetails { - public string Id { get; set; } = null!; - public string Name { get; set; } = null!; - public string Description { get; set; } = null!; + [Id(0)] + public string? Id { get; set; } + [Id(1)] + public string? Name { get; set; } + [Id(2)] + public string? Description { get; set; } + [Id(3)] public ProductCategory Category { get; set; } + [Id(4)] public int Quantity { get; set; } + [Id(5)] public decimal UnitPrice { get; set; } - public string DetailsUrl { get; set; } = null!; - public string ImageUrl { get; set; } = null!; + [Id(6)] + public string? DetailsUrl { get; set; } + [Id(7)] + public string? ImageUrl { get; set; } [JsonIgnore] public decimal TotalPrice => diff --git a/Grains/InventoryGrain.cs b/Grains/InventoryGrain.cs index da35643..c3b7509 100644 --- a/Grains/InventoryGrain.cs +++ b/Grains/InventoryGrain.cs @@ -22,6 +22,7 @@ Task> IInventoryGrain.GetAllProductsAsync() => async Task IInventoryGrain.AddOrUpdateProductAsync(ProductDetails product) { + ArgumentNullException.ThrowIfNull(product.Id); _productIds.State.Add(product.Id); _productCache[product.Id] = product; diff --git a/Grains/Orleans.ShoppingCart.Grains.csproj b/Grains/Orleans.ShoppingCart.Grains.csproj index 62eb87d..4eb0510 100644 --- a/Grains/Orleans.ShoppingCart.Grains.csproj +++ b/Grains/Orleans.ShoppingCart.Grains.csproj @@ -1,16 +1,16 @@ - net7.0 + net8.0 enable enable - + - - + + diff --git a/Grains/ProductGrain.cs b/Grains/ProductGrain.cs index de1a553..a47d797 100644 --- a/Grains/ProductGrain.cs +++ b/Grains/ProductGrain.cs @@ -47,6 +47,7 @@ Task IProductGrain.CreateOrUpdateProductAsync(ProductDetails productDetails) => private async Task UpdateStateAsync(ProductDetails product) { + ArgumentNullException.ThrowIfNull(product.Id); var oldCategory = _product.State.Category; _product.State = product; diff --git a/Grains/ShoppingCartGrain.cs b/Grains/ShoppingCartGrain.cs index 1f75fd3..e5f774c 100644 --- a/Grains/ShoppingCartGrain.cs +++ b/Grains/ShoppingCartGrain.cs @@ -16,6 +16,7 @@ public ShoppingCartGrain( async Task IShoppingCartGrain.AddOrUpdateItemAsync(int quantity, ProductDetails product) { + ArgumentNullException.ThrowIfNull(product.Id); var products = GrainFactory.GetGrain(product.Id); int? adjustedQuantity = null; @@ -29,7 +30,10 @@ async Task IShoppingCartGrain.AddOrUpdateItemAsync(int quantity, ProductDe if (isAvailable && claimedProduct is not null) { var item = ToCartItem(quantity, claimedProduct); - _cart.State[claimedProduct.Id] = item; + if (!string.IsNullOrEmpty(claimedProduct.Id)) + { + _cart.State[claimedProduct.Id] = item; + } await _cart.WriteStateAsync(); return true; @@ -52,6 +56,7 @@ Task IShoppingCartGrain.GetTotalItemsInCartAsync() => async Task IShoppingCartGrain.RemoveItemAsync(ProductDetails product) { + ArgumentNullException.ThrowIfNull(product.Id); var products = GrainFactory.GetGrain(product.Id); await products.ReturnProductAsync(product.Quantity); diff --git a/Silo/App.razor b/Silo/App.razor index e0d13cc..c5ac499 100644 --- a/Silo/App.razor +++ b/Silo/App.razor @@ -1,4 +1,4 @@ - + diff --git a/Silo/Components/ManageProductModal.razor b/Silo/Components/ManageProductModal.razor index b253b6a..e7c93d5 100644 --- a/Silo/Components/ManageProductModal.razor +++ b/Silo/Components/ManageProductModal.razor @@ -63,7 +63,7 @@ public void Close() => MudDialog?.Cancel(); - private void Bogus() => Product = Product.GetBogusFaker().Generate(); + private void Bogus() => Product = ProductDetailsExtensions.ProductDetailsFaker.Generate(); private Task Save() { diff --git a/Silo/Components/ProductTable.razor b/Silo/Components/ProductTable.razor index 23e171c..a2a8356 100644 --- a/Silo/Components/ProductTable.razor +++ b/Silo/Components/ProductTable.razor @@ -1,13 +1,13 @@ @using Blazor.Serialization.Extensions - + @Title @ChildContent - + + AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="d-flex justify-center" Underline=false> @@ -18,22 +18,22 @@ IsEditRowSwitchingBlocked=true> - (p => p.Name))> + (p => p.Name!))> Name - (p => p.Description))> + (p => p.Description!))> Description - (p => p.Quantity))> + (p => p.Quantity!))> Quantity - (p => p.UnitPrice))> + (p => p.UnitPrice!))> Price diff --git a/Silo/Components/PurchasableProductTable.razor b/Silo/Components/PurchasableProductTable.razor index 3d936a9..5286c00 100644 --- a/Silo/Components/PurchasableProductTable.razor +++ b/Silo/Components/PurchasableProductTable.razor @@ -1,10 +1,10 @@ - + @Title - + + AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="d-flex justify-center" Underline="false"> @@ -13,22 +13,22 @@ Products - (p => p.Name))> + (p => p.Name!))> Name - (p => p.Description))> + (p => p.Description!))> Description - (p => p.Quantity))> + (p => p.Quantity!))> Quantity - (p => p.UnitPrice))> + (p => p.UnitPrice!))> Price @@ -37,7 +37,7 @@ await AddToCartAsync(product.Id))/> + OnClick="@(() => AddToCartAsync(product.Id!))"/> @product.Name @product.Description @@ -65,10 +65,13 @@ [Parameter, EditorRequired] public Func IsInCart { get; set; } = null!; - Task AddToCartAsync(string productId) => - OnAddedToCart.HasDelegate - ? OnAddedToCart.InvokeAsync(productId) - : Task.CompletedTask; + async Task AddToCartAsync(string productId) + { + if (OnAddedToCart is { HasDelegate: true } onAddedToCart) + { + await onAddedToCart.InvokeAsync(productId); + } + } bool OnFilter(ProductDetails product) => product.MatchesFilter(_filter); } \ No newline at end of file diff --git a/Silo/Components/ShoppingCartSummary.razor b/Silo/Components/ShoppingCartSummary.razor index 835bd91..ae32c1e 100644 --- a/Silo/Components/ShoppingCartSummary.razor +++ b/Silo/Components/ShoppingCartSummary.razor @@ -31,7 +31,7 @@ @_totalCost Checkout diff --git a/Silo/Extensions/ProductDetailsExtensions.cs b/Silo/Extensions/ProductDetailsExtensions.cs index ee7cd39..eaf11f6 100644 --- a/Silo/Extensions/ProductDetailsExtensions.cs +++ b/Silo/Extensions/ProductDetailsExtensions.cs @@ -5,7 +5,7 @@ namespace Orleans.ShoppingCart.Silo.Extensions; internal static class ProductDetailsExtensions { - internal static Faker GetBogusFaker(this ProductDetails productDetails) => + internal static readonly Faker ProductDetailsFaker = new Faker() .StrictMode(true) .RuleFor(p => p.Id, (f, p) => f.Random.Number(1, 100_000).ToString()) @@ -26,8 +26,8 @@ internal static bool MatchesFilter(this ProductDetails product, string? filter) if (product is not null) { - return product.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) - || product.Description.Contains(filter, StringComparison.OrdinalIgnoreCase); + return (product.Name is { } name && name.Contains(filter, StringComparison.OrdinalIgnoreCase)) + || (product.Description is { } description && description.Contains(filter, StringComparison.OrdinalIgnoreCase)); } return false; diff --git a/Silo/GlobalUsings.cs b/Silo/GlobalUsings.cs index b059d37..0351c76 100644 --- a/Silo/GlobalUsings.cs +++ b/Silo/GlobalUsings.cs @@ -10,10 +10,7 @@ global using MudBlazor; global using MudBlazor.Services; global using Orleans.Configuration; -global using Orleans.Hosting; -global using Orleans.Runtime; global using Orleans.ShoppingCart.Abstractions; -global using Orleans.ShoppingCart.Silo; global using Orleans.ShoppingCart.Silo.Extensions; global using Orleans.ShoppingCart.Silo.Services; global using Orleans.ShoppingCart.Silo.StartupTasks; diff --git a/Silo/Orleans.ShoppingCart.Silo.csproj b/Silo/Orleans.ShoppingCart.Silo.csproj index 3e4fb14..dcd42ee 100644 --- a/Silo/Orleans.ShoppingCart.Silo.csproj +++ b/Silo/Orleans.ShoppingCart.Silo.csproj @@ -1,23 +1,23 @@  - net7.0 + net8.0 enable enable false - - - + + + - - - + + + - + diff --git a/Silo/Pages/Cart.razor b/Silo/Pages/Cart.razor index 39e1680..efd548d 100644 --- a/Silo/Pages/Cart.razor +++ b/Silo/Pages/Cart.razor @@ -1,6 +1,6 @@ @page "/cart" - + Shopping Cart Welcome to the Orleans Shopping Cart - + Use the Shop Inventory link to add items to your cart. diff --git a/Silo/Pages/Products.razor b/Silo/Pages/Products.razor index 2ea1df7..697ab99 100644 --- a/Silo/Pages/Products.razor +++ b/Silo/Pages/Products.razor @@ -2,7 +2,7 @@ + Color="Color.Primary" Size="Size.Large" DropShadow="false" OnClick=@CreateNewProduct> Create New Product diff --git a/Silo/Pages/Products.razor.cs b/Silo/Pages/Products.razor.cs index efff6ef..935154d 100644 --- a/Silo/Pages/Products.razor.cs +++ b/Silo/Pages/Products.razor.cs @@ -35,7 +35,7 @@ private void CreateNewProduct() private async Task OnProductUpdated(ProductDetails product) { - var fake = faker.Generate(); + var fake = ProductDetailsExtensions.ProductDetailsFaker.Generate(); product = product with { Id = fake.Id, diff --git a/Silo/Pages/_Host.cshtml b/Silo/Pages/_Host.cshtml index fec90ef..645a067 100644 --- a/Silo/Pages/_Host.cshtml +++ b/Silo/Pages/_Host.cshtml @@ -1,5 +1,6 @@ @page "/" @namespace Orleans.ShoppingCart.Pages +@using Orleans.ShoppingCart.Silo @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @{ Layout = null; diff --git a/Silo/Program.cs b/Silo/Program.cs index 1ea4e3b..a7475ab 100644 --- a/Silo/Program.cs +++ b/Silo/Program.cs @@ -1,50 +1,80 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT License. -await Host.CreateDefaultBuilder(args) - .UseOrleans( - (context, builder) => +using Azure.Data.Tables; + +var builder = WebApplication.CreateBuilder(args); +builder.UseOrleans(siloBuilder => +{ + siloBuilder.AddStartupTask(); + if (builder.Environment.IsDevelopment()) + { + siloBuilder.UseLocalhostClustering() + .AddMemoryGrainStorage("shopping-cart"); + } + else + { + var endpointAddress = IPAddress.Parse(builder.Configuration["WEBSITE_PRIVATE_IP"]!); + var strPorts = builder.Configuration["WEBSITE_PRIVATE_PORTS"]!.Split(','); + if (strPorts.Length < 2) { - if (context.HostingEnvironment.IsDevelopment()) - { - builder.UseLocalhostClustering() - .AddMemoryGrainStorage("shopping-cart") - .AddStartupTask(); - } - else - { - var endpointAddress = - IPAddress.Parse(context.Configuration["WEBSITE_PRIVATE_IP"]!); - var strPorts = - context.Configuration["WEBSITE_PRIVATE_PORTS"]!.Split(','); - if (strPorts.Length < 2) - throw new Exception("Insufficient private ports configured."); - var (siloPort, gatewayPort) = - (int.Parse(strPorts[0]), int.Parse(strPorts[1])); - var connectionString = - context.Configuration["ORLEANS_AZURE_STORAGE_CONNECTION_STRING"]; - - builder - .ConfigureEndpoints(endpointAddress, siloPort, gatewayPort) - .Configure( - options => - { - options.ClusterId = context.Configuration["ORLEANS_CLUSTER_ID"]; - options.ServiceId = nameof(ShoppingCartService); - }) - .UseAzureStorageClustering( - options => - { - options.ConfigureTableServiceClient(connectionString); - options.TableName = $"{context.Configuration["ORLEANS_CLUSTER_ID"]}Clustering"; - }) - .AddAzureTableGrainStorage("shopping-cart", - options => { - options.ConfigureTableServiceClient(connectionString); - options.TableName = $"{context.Configuration["ORLEANS_CLUSTER_ID"]}Persistence"; - }); - } - }) - .ConfigureWebHostDefaults( - webBuilder => webBuilder.UseStartup()) - .RunConsoleAsync(); \ No newline at end of file + throw new Exception("Insufficient private ports configured."); + } + + var (siloPort, gatewayPort) = (int.Parse(strPorts[0]), int.Parse(strPorts[1])); + + siloBuilder + .ConfigureEndpoints(endpointAddress, siloPort, gatewayPort, listenOnAnyHostAddress: true) + .Configure( + options => + { + options.ClusterId = builder.Configuration["ORLEANS_CLUSTER_ID"]; + options.ServiceId = nameof(ShoppingCartService); + }) + .UseAzureStorageClustering( + options => + { + options.TableServiceClient = new TableServiceClient(builder.Configuration["ORLEANS_AZURE_STORAGE_CONNECTION_STRING"]); + options.TableName = $"{builder.Configuration["ORLEANS_CLUSTER_ID"]}Clustering"; + }) + .AddAzureTableGrainStorage("shopping-cart", + options => + { + options.TableServiceClient = new TableServiceClient(builder.Configuration["ORLEANS_AZURE_STORAGE_CONNECTION_STRING"]); + options.TableName = $"{builder.Configuration["ORLEANS_CLUSTER_ID"]}Persistence"; + }); + } +}); + +var services = builder.Services; +services.AddMudServices(); +services.AddRazorPages(); +services.AddServerSideBlazor(); +services.AddHttpContextAccessor(); +services.AddSingleton(); +services.AddSingleton(); +services.AddSingleton(); +services.AddScoped(); +services.AddSingleton(); +services.AddLocalStorageServices(); +services.AddApplicationInsights("Silo"); + +var app = builder.Build(); + +if (builder.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} +else +{ + app.UseExceptionHandler("/Error"); + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseRouting(); + +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); +await app.RunAsync(); diff --git a/Silo/Shared/MainLayout.razor b/Silo/Shared/MainLayout.razor index a6eac94..23f20ba 100644 --- a/Silo/Shared/MainLayout.razor +++ b/Silo/Shared/MainLayout.razor @@ -9,20 +9,23 @@ FullWidth="true" MaxWidth="MaxWidth.Medium" CloseButton="true" - DisableBackdropClick="false" + BackdropClick="true" Position="DialogPosition.Center" CloseOnEscapeKey="true" /> + + Icon=@Icons.Material.Outlined.DarkMode Color=@Color.Inherit + title="@(_isDarkTheme ? "Switch to Light Theme" : "Switch to Dark Theme")" + ToggledIcon=@Icons.Material.Filled.WbSunny + ToggledColor=@Color.Default /> diff --git a/Silo/Shared/MainLayout.razor.cs b/Silo/Shared/MainLayout.razor.cs index 0fa2d6e..e1528d8 100644 --- a/Silo/Shared/MainLayout.razor.cs +++ b/Silo/Shared/MainLayout.razor.cs @@ -11,20 +11,20 @@ public partial class MainLayout readonly MudTheme _theme = new() { - Palette = new Palette() + PaletteLight = new PaletteLight() { Tertiary = "#7e6fff", DrawerIcon = "#aaa9b9", DrawerText = "#aaa9b9", DrawerBackground = "#303030" }, - PaletteDark = new Palette() + PaletteDark = new PaletteDark() { Primary = "#7e6fff", Tertiary = "#7e6fff", Surface = "#1e1e2d", Background = "#1a1a27", - BackgroundGrey = "#151521", + BackgroundGray = "#151521", AppbarText = "#92929f", AppbarBackground = "rgba(26,26,39,0.8)", DrawerBackground = "#1a1a27", diff --git a/Silo/Startup.cs b/Silo/Startup.cs deleted file mode 100644 index 2938cfb..0000000 --- a/Silo/Startup.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT License. - -namespace Orleans.ShoppingCart.Silo; - -public sealed class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddMudServices(); - services.AddRazorPages(); - services.AddServerSideBlazor(); - services.AddHttpContextAccessor(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddScoped(); - services.AddSingleton(); - services.AddLocalStorageServices(); - services.AddApplicationInsights("Silo"); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapBlazorHub(); - endpoints.MapFallbackToPage("/_Host"); - }); - } -} \ No newline at end of file diff --git a/Silo/StartupTasks/SeedProductStoreTask.cs b/Silo/StartupTasks/SeedProductStoreTask.cs index 3663ce9..ea6e5fa 100644 --- a/Silo/StartupTasks/SeedProductStoreTask.cs +++ b/Silo/StartupTasks/SeedProductStoreTask.cs @@ -11,8 +11,8 @@ public SeedProductStoreTask(IGrainFactory grainFactory) => _grainFactory = grainFactory; async Task IStartupTask.Execute(CancellationToken cancellationToken) - { - var faker = new ProductDetails().GetBogusFaker(); + { + var faker = ProductDetailsExtensions.ProductDetailsFaker; foreach (var product in faker.GenerateLazy(50)) { diff --git a/Silo/web.config b/Silo/web.config deleted file mode 100644 index 5d42b8c..0000000 --- a/Silo/web.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/global.json b/global.json index 006566e..9df6ea1 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.202", + "version": "8.0.402", "rollForward": "latestFeature" } } \ No newline at end of file