diff --git a/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs b/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs index 0798f85e3..d32660005 100644 --- a/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs +++ b/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs @@ -81,6 +81,14 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http var response = await _client.SendAsync(requestMessage); + // raise exception if HttpResponseCode 500 + // needed for circuit breaker to track fails + + if (response.StatusCode == HttpStatusCode.InternalServerError) + { + throw new HttpRequestException(); + } + return await response.Content.ReadAsStringAsync(); }); } diff --git a/src/Services/Basket/Basket.API/Program.cs b/src/Services/Basket/Basket.API/Program.cs index 3eeeb2479..7753ef537 100644 --- a/src/Services/Basket/Basket.API/Program.cs +++ b/src/Services/Basket/Basket.API/Program.cs @@ -14,9 +14,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API .UseKestrel() .UseFailing(options => { - options.ConfigPath = "/Failing"; - options.EndpointPaths = new List() - { "/api/v1/basket/checkout", "/hc" }; + options.ConfigPath = "/Failing"; }) .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) diff --git a/src/Web/WebMVC/Controllers/CartController.cs b/src/Web/WebMVC/Controllers/CartController.cs index 9e5aa0a91..a7bb84959 100644 --- a/src/Web/WebMVC/Controllers/CartController.cs +++ b/src/Web/WebMVC/Controllers/CartController.cs @@ -7,6 +7,7 @@ using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authentication; +using Polly.CircuitBreaker; namespace Microsoft.eShopOnContainers.WebMVC.Controllers { @@ -26,47 +27,79 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers public async Task Index() { - var user = _appUserParser.Parse(HttpContext.User); - var vm = await _basketSvc.GetBasket(user); - + try + { + var user = _appUserParser.Parse(HttpContext.User); + var vm = await _basketSvc.GetBasket(user); - return View(vm); + return View(vm); + } + catch (BrokenCircuitException) + { + // Catch error when Basket.api is in circuit-opened mode + HandleBrokenCircuitException(); + } + + return View(); } [HttpPost] public async Task Index(Dictionary quantities, string action) { - var user = _appUserParser.Parse(HttpContext.User); - var basket = await _basketSvc.SetQuantities(user, quantities); - var vm = await _basketSvc.UpdateBasket(basket); + try + { + var user = _appUserParser.Parse(HttpContext.User); + var basket = await _basketSvc.SetQuantities(user, quantities); + var vm = await _basketSvc.UpdateBasket(basket); - if (action == "[ Checkout ]") + if (action == "[ Checkout ]") + { + var order = _basketSvc.MapBasketToOrder(basket); + return RedirectToAction("Create", "Order"); + } + } + catch (BrokenCircuitException) { - var order = _basketSvc.MapBasketToOrder(basket); - return RedirectToAction("Create", "Order"); + // Catch error when Basket.api is in circuit-opened mode + HandleBrokenCircuitException(); } - - return View(vm); + + return View(); } public async Task AddToCart(CatalogItem productDetails) { - if (productDetails.Id != null) + try { - var user = _appUserParser.Parse(HttpContext.User); - var product = new BasketItem() + if (productDetails.Id != null) { - Id = Guid.NewGuid().ToString(), - Quantity = 1, - ProductName = productDetails.Name, - PictureUrl = productDetails.PictureUri, - UnitPrice = productDetails.Price, - ProductId = productDetails.Id - }; - await _basketSvc.AddItemToBasket(user, product); + var user = _appUserParser.Parse(HttpContext.User); + var product = new BasketItem() + { + Id = Guid.NewGuid().ToString(), + Quantity = 1, + ProductName = productDetails.Name, + PictureUrl = productDetails.PictureUri, + UnitPrice = productDetails.Price, + ProductId = productDetails.Id + }; + await _basketSvc.AddItemToBasket(user, product); + } + return RedirectToAction("Index", "Catalog"); + } + catch (BrokenCircuitException) + { + // Catch error when Basket.api is in circuit-opened mode + HandleBrokenCircuitException(); } + return RedirectToAction("Index", "Catalog"); } + + private void HandleBrokenCircuitException() + { + TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business Msg Due to Circuit-Breaker)"; + } } } diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index dfbe99652..75b7b1e9b 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -40,7 +40,8 @@ namespace Microsoft.eShopOnContainers.WebMVC // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc(); + services.AddSession(); if (Configuration.GetValue("IsClusterEnv") == bool.TrueString) { @@ -104,6 +105,7 @@ namespace Microsoft.eShopOnContainers.WebMVC app.UseExceptionHandler("/Catalog/Error"); } + app.UseSession(); app.UseStaticFiles(); app.UseCookieAuthentication(new CookieAuthenticationOptions diff --git a/src/Web/WebMVC/ViewComponents/Cart.cs b/src/Web/WebMVC/ViewComponents/Cart.cs index 4e5aae38f..05c7dac50 100644 --- a/src/Web/WebMVC/ViewComponents/Cart.cs +++ b/src/Web/WebMVC/ViewComponents/Cart.cs @@ -29,7 +29,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents { // Catch error when Basket.api is in circuit-opened mode ViewBag.IsBasketInoperative = true; - vm.ItemsCount = 0; } return View(vm); diff --git a/src/Web/WebMVC/ViewComponents/CartList.cs b/src/Web/WebMVC/ViewComponents/CartList.cs index 099cc8a3d..04a7a783a 100644 --- a/src/Web/WebMVC/ViewComponents/CartList.cs +++ b/src/Web/WebMVC/ViewComponents/CartList.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Polly.CircuitBreaker; namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents { @@ -16,8 +17,19 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents public async Task InvokeAsync(ApplicationUser user) { - var item = await GetItemsAsync(user); - return View(item); + var vm = new Basket(); + try + { + vm = await GetItemsAsync(user); + return View(vm); + } + catch (BrokenCircuitException) + { + // Catch error when Basket.api is in circuit-opened mode + TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business Msg Due to Circuit-Breaker)"; + } + + return View(vm); } private Task GetItemsAsync(ApplicationUser user) => _cartSvc.GetBasket(user); diff --git a/src/Web/WebMVC/Views/Catalog/Index.cshtml b/src/Web/WebMVC/Views/Catalog/Index.cshtml index ddac94196..23667500f 100644 --- a/src/Web/WebMVC/Views/Catalog/Index.cshtml +++ b/src/Web/WebMVC/Views/Catalog/Index.cshtml @@ -23,6 +23,15 @@
+
+
+ @if(TempData.ContainsKey("BasketInoperativeMsg")) + { + + } +
@if (Model.CatalogItems.Count() > 0) { diff --git a/src/Web/WebMVC/Views/Shared/Components/Cart/Default.cshtml b/src/Web/WebMVC/Views/Shared/Components/Cart/Default.cshtml index 6ad90c81d..bffed722b 100644 --- a/src/Web/WebMVC/Views/Shared/Components/Cart/Default.cshtml +++ b/src/Web/WebMVC/Views/Shared/Components/Cart/Default.cshtml @@ -7,18 +7,21 @@ -
- -
+ asp-action="Index"> @if (ViewBag.IsBasketInoperative == true) { -
- @Model.ItemsCount +
+
+
+ X +
} else { +
+ +
@Model.ItemsCount
diff --git a/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml b/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml index 1f0d05320..091365e1e 100644 --- a/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml +++ b/src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml @@ -5,67 +5,87 @@ }
-
-
Product
-
-
Price
-
Quantity
-
Cost
-
- - @for (int i = 0; i < Model.Items.Count; i++) + @if (TempData.ContainsKey("BasketInoperativeMsg")) + { +
+ + } + else { - var item = Model.Items[i]; +
+
+ @if (TempData.ContainsKey("BasketInoperativeMsg")) + { + + } -
-
-
- -
-
@item.ProductName
-
$ @item.UnitPrice.ToString("N2")
-
- - -
-
$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")
+
Product
+
+
Price
+
Quantity
+
Cost
+
+ + @for (int i = 0; i < Model.Items.Count; i++) + { + var item = Model.Items[i]; + +
+
+
+ +
+
@item.ProductName
+
$ @item.UnitPrice.ToString("N2")
+
+ + +
+
$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")
+
+
+ +
+
+ +
+ @if (item.OldUnitPrice != 0) + { + + }
-
+
+ } -
-
+
+
+
+
Total
+
-
- @if (item.OldUnitPrice != 0) - { - - } +
+
+
$ @Model.Total()
+
+ +
+
+
+ +
+
+ +
+
-
- } +
-
-
-
-
Total
-
- -
-
-
$ @Model.Total()
-
- -
-
-
- -
-
- -
-
-
+ diff --git a/src/Web/WebMVC/WebMVC.csproj b/src/Web/WebMVC/WebMVC.csproj index c0cc08da0..f36270750 100644 --- a/src/Web/WebMVC/WebMVC.csproj +++ b/src/Web/WebMVC/WebMVC.csproj @@ -41,6 +41,7 @@ + diff --git a/src/Web/WebMVC/wwwroot/images/cart-inoperative.png b/src/Web/WebMVC/wwwroot/images/cart-inoperative.png new file mode 100644 index 000000000..24a2e18f7 Binary files /dev/null and b/src/Web/WebMVC/wwwroot/images/cart-inoperative.png differ