Browse Source

Added UI alert and circuit-breaker handlers for components that depend on Basket.api

pull/241/head
Ramón Tomás 7 years ago
parent
commit
d42033a83b
11 changed files with 176 additions and 91 deletions
  1. +8
    -0
      src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs
  2. +1
    -3
      src/Services/Basket/Basket.API/Program.cs
  3. +56
    -23
      src/Web/WebMVC/Controllers/CartController.cs
  4. +3
    -1
      src/Web/WebMVC/Startup.cs
  5. +0
    -1
      src/Web/WebMVC/ViewComponents/Cart.cs
  6. +14
    -2
      src/Web/WebMVC/ViewComponents/CartList.cs
  7. +9
    -0
      src/Web/WebMVC/Views/Catalog/Index.cshtml
  8. +9
    -6
      src/Web/WebMVC/Views/Shared/Components/Cart/Default.cshtml
  9. +75
    -55
      src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml
  10. +1
    -0
      src/Web/WebMVC/WebMVC.csproj
  11. BIN
      src/Web/WebMVC/wwwroot/images/cart-inoperative.png

+ 8
- 0
src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs View File

@ -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();
});
}


+ 1
- 3
src/Services/Basket/Basket.API/Program.cs View File

@ -14,9 +14,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
.UseKestrel()
.UseFailing(options =>
{
options.ConfigPath = "/Failing";
options.EndpointPaths = new List<string>()
{ "/api/v1/basket/checkout", "/hc" };
options.ConfigPath = "/Failing";
})
.UseHealthChecks("/hc")
.UseContentRoot(Directory.GetCurrentDirectory())


+ 56
- 23
src/Web/WebMVC/Controllers/CartController.cs View File

@ -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<IActionResult> 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<IActionResult> Index(Dictionary<string, int> 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<IActionResult> 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)";
}
}
}

+ 3
- 1
src/Web/WebMVC/Startup.cs View File

@ -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<string>("IsClusterEnv") == bool.TrueString)
{
@ -104,6 +105,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
app.UseExceptionHandler("/Catalog/Error");
}
app.UseSession();
app.UseStaticFiles();
app.UseCookieAuthentication(new CookieAuthenticationOptions


+ 0
- 1
src/Web/WebMVC/ViewComponents/Cart.cs View File

@ -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);


+ 14
- 2
src/Web/WebMVC/ViewComponents/CartList.cs View File

@ -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<IViewComponentResult> 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<Basket> GetItemsAsync(ApplicationUser user) => _cartSvc.GetBasket(user);


+ 9
- 0
src/Web/WebMVC/Views/Catalog/Index.cshtml View File

@ -23,6 +23,15 @@
</section>
<div class="container">
<div class="row">
<br />
@if(TempData.ContainsKey("BasketInoperativeMsg"))
{
<div class="alert alert-warning" role="alert">
&nbsp;@TempData["BasketInoperativeMsg"]
</div>
}
</div>
@if (Model.CatalogItems.Count() > 0)
{


+ 9
- 6
src/Web/WebMVC/Views/Shared/Components/Cart/Default.cshtml View File

@ -7,18 +7,21 @@
<a class="esh-basketstatus @Model.Disabled"
asp-area=""
asp-controller="Cart"
asp-action="Index">
<div class="esh-basketstatus-image">
<img src="~/images/cart.png" />
</div>
asp-action="Index">
@if (ViewBag.IsBasketInoperative == true)
{
<div class="esh-basketstatus-badge-inoperative">
@Model.ItemsCount
<div class="esh-basketstatus-image">
<img src="~/images/cart-inoperative.png" />
</div>
<div class="esh-basketstatus-badge-inoperative">
X
</div>
}
else
{
<div class="esh-basketstatus-image">
<img src="~/images/cart.png" />
</div>
<div class="esh-basketstatus-badge">
@Model.ItemsCount
</div>


+ 75
- 55
src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml View File

@ -5,67 +5,87 @@
}
<div class="container">
<article class="esh-basket-titles row">
<section class="esh-basket-title col-xs-3">Product</section>
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
<section class="esh-basket-title col-xs-2">Price</section>
<section class="esh-basket-title col-xs-2">Quantity</section>
<section class="esh-basket-title col-xs-2">Cost</section>
</article>
@for (int i = 0; i < Model.Items.Count; i++)
@if (TempData.ContainsKey("BasketInoperativeMsg"))
{
<br />
<div class="alert alert-warning" role="alert">
&nbsp;@TempData["BasketInoperativeMsg"]
</div>
}
else
{
var item = Model.Items[i];
<article class="esh-basket-titles row">
<br />
@if (TempData.ContainsKey("BasketInoperativeMsg"))
{
<div class="alert alert-warning" role="alert">
&nbsp;@TempData["BasketInoperativeMsg"]
</div>
}
<article class="esh-basket-items row">
<div>
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="@item.PictureUrl" />
</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
</section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")</section>
<section class="esh-basket-title col-xs-3">Product</section>
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
<section class="esh-basket-title col-xs-2">Price</section>
<section class="esh-basket-title col-xs-2">Quantity</section>
<section class="esh-basket-title col-xs-2">Cost</section>
</article>
@for (int i = 0; i < Model.Items.Count; i++)
{
var item = Model.Items[i];
<article class="esh-basket-items row">
<div>
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="@item.PictureUrl" />
</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
</section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")</section>
</div>
<div class="row">
</div>
</article>
<div class="esh-basket-items--border row">
@if (item.OldUnitPrice != 0)
{
<div class="alert alert-warning esh-basket-margin12" role="alert">&nbsp;Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ @item.OldUnitPrice </div>
}
</div>
<div class="row">
<br />
}
</div>
</article>
<div class="container">
<article class="esh-basket-titles esh-basket-titles--clean row">
<section class="esh-basket-title col-xs-10"></section>
<section class="esh-basket-title col-xs-2">Total</section>
</article>
<div class="esh-basket-items--border row">
@if (item.OldUnitPrice != 0)
{
<div class="alert alert-warning esh-basket-margin12" role="alert">&nbsp;Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ @item.OldUnitPrice </div>
}
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-10"></section>
<section class="esh-basket-item esh-basket-item--mark col-xs-2">$ @Model.Total()</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-7"></section>
<section class="esh-basket-item col-xs-2">
<button class="btn esh-basket-checkout" name="name" value="" type="submit">[ Update ]</button>
</section>
<section class="esh-basket-item col-xs-3">
<input type="submit"
class="btn esh-basket-checkout"
value="[ Checkout ]" name="action" />
</section>
</article>
</div>
<br/>
}
</div>
<div class="container">
<article class="esh-basket-titles esh-basket-titles--clean row">
<section class="esh-basket-title col-xs-10"></section>
<section class="esh-basket-title col-xs-2">Total</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-10"></section>
<section class="esh-basket-item esh-basket-item--mark col-xs-2">$ @Model.Total()</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-7"></section>
<section class="esh-basket-item col-xs-2">
<button class="btn esh-basket-checkout" name="name" value="" type="submit">[ Update ]</button>
</section>
<section class="esh-basket-item col-xs-3">
<input type="submit"
class="btn esh-basket-checkout"
value="[ Checkout ]" name="action" />
</section>
</article>
</div>

+ 1
- 0
src/Web/WebMVC/WebMVC.csproj View File

@ -41,6 +41,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Session" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" />


BIN
src/Web/WebMVC/wwwroot/images/cart-inoperative.png View File

Before After
Width: 43  |  Height: 37  |  Size: 948 B

Loading…
Cancel
Save