Added UI alert and circuit-breaker handlers for components that depend on Basket.api
This commit is contained in:
parent
f39fdb92ca
commit
d42033a83b
@ -81,6 +81,14 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
|||||||
|
|
||||||
var response = await _client.SendAsync(requestMessage);
|
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();
|
return await response.Content.ReadAsStringAsync();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
|||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.UseFailing(options =>
|
.UseFailing(options =>
|
||||||
{
|
{
|
||||||
options.ConfigPath = "/Failing";
|
options.ConfigPath = "/Failing";
|
||||||
options.EndpointPaths = new List<string>()
|
|
||||||
{ "/api/v1/basket/checkout", "/hc" };
|
|
||||||
})
|
})
|
||||||
.UseHealthChecks("/hc")
|
.UseHealthChecks("/hc")
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
@ -7,6 +7,7 @@ using Microsoft.eShopOnContainers.WebMVC.Services;
|
|||||||
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Polly.CircuitBreaker;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
||||||
{
|
{
|
||||||
@ -26,47 +27,79 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
|||||||
|
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
var user = _appUserParser.Parse(HttpContext.User);
|
try
|
||||||
var vm = await _basketSvc.GetBasket(user);
|
{
|
||||||
|
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]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Index(Dictionary<string, int> quantities, string action)
|
public async Task<IActionResult> Index(Dictionary<string, int> quantities, string action)
|
||||||
{
|
{
|
||||||
var user = _appUserParser.Parse(HttpContext.User);
|
try
|
||||||
var basket = await _basketSvc.SetQuantities(user, quantities);
|
|
||||||
var vm = await _basketSvc.UpdateBasket(basket);
|
|
||||||
|
|
||||||
if (action == "[ Checkout ]")
|
|
||||||
{
|
{
|
||||||
var order = _basketSvc.MapBasketToOrder(basket);
|
var user = _appUserParser.Parse(HttpContext.User);
|
||||||
return RedirectToAction("Create", "Order");
|
var basket = await _basketSvc.SetQuantities(user, quantities);
|
||||||
|
var vm = await _basketSvc.UpdateBasket(basket);
|
||||||
|
|
||||||
|
if (action == "[ Checkout ]")
|
||||||
|
{
|
||||||
|
var order = _basketSvc.MapBasketToOrder(basket);
|
||||||
|
return RedirectToAction("Create", "Order");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (BrokenCircuitException)
|
||||||
return View(vm);
|
{
|
||||||
|
// Catch error when Basket.api is in circuit-opened mode
|
||||||
|
HandleBrokenCircuitException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> AddToCart(CatalogItem productDetails)
|
public async Task<IActionResult> AddToCart(CatalogItem productDetails)
|
||||||
{
|
{
|
||||||
if (productDetails.Id != null)
|
try
|
||||||
{
|
{
|
||||||
var user = _appUserParser.Parse(HttpContext.User);
|
if (productDetails.Id != null)
|
||||||
var product = new BasketItem()
|
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid().ToString(),
|
var user = _appUserParser.Parse(HttpContext.User);
|
||||||
Quantity = 1,
|
var product = new BasketItem()
|
||||||
ProductName = productDetails.Name,
|
{
|
||||||
PictureUrl = productDetails.PictureUri,
|
Id = Guid.NewGuid().ToString(),
|
||||||
UnitPrice = productDetails.Price,
|
Quantity = 1,
|
||||||
ProductId = productDetails.Id
|
ProductName = productDetails.Name,
|
||||||
};
|
PictureUrl = productDetails.PictureUri,
|
||||||
await _basketSvc.AddItemToBasket(user, product);
|
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");
|
return RedirectToAction("Index", "Catalog");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleBrokenCircuitException()
|
||||||
|
{
|
||||||
|
TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business Msg Due to Circuit-Breaker)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,8 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddMvc();
|
services.AddMvc();
|
||||||
|
services.AddSession();
|
||||||
|
|
||||||
if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString)
|
if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString)
|
||||||
{
|
{
|
||||||
@ -104,6 +105,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
app.UseExceptionHandler("/Catalog/Error");
|
app.UseExceptionHandler("/Catalog/Error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.UseSession();
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
app.UseCookieAuthentication(new CookieAuthenticationOptions
|
app.UseCookieAuthentication(new CookieAuthenticationOptions
|
||||||
|
@ -29,7 +29,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents
|
|||||||
{
|
{
|
||||||
// Catch error when Basket.api is in circuit-opened mode
|
// Catch error when Basket.api is in circuit-opened mode
|
||||||
ViewBag.IsBasketInoperative = true;
|
ViewBag.IsBasketInoperative = true;
|
||||||
vm.ItemsCount = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return View(vm);
|
return View(vm);
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Polly.CircuitBreaker;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents
|
namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents
|
||||||
{
|
{
|
||||||
@ -16,8 +17,19 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents
|
|||||||
|
|
||||||
public async Task<IViewComponentResult> InvokeAsync(ApplicationUser user)
|
public async Task<IViewComponentResult> InvokeAsync(ApplicationUser user)
|
||||||
{
|
{
|
||||||
var item = await GetItemsAsync(user);
|
var vm = new Basket();
|
||||||
return View(item);
|
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);
|
private Task<Basket> GetItemsAsync(ApplicationUser user) => _cartSvc.GetBasket(user);
|
||||||
|
@ -23,6 +23,15 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<br />
|
||||||
|
@if(TempData.ContainsKey("BasketInoperativeMsg"))
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
@TempData["BasketInoperativeMsg"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (Model.CatalogItems.Count() > 0)
|
@if (Model.CatalogItems.Count() > 0)
|
||||||
{
|
{
|
||||||
|
@ -7,18 +7,21 @@
|
|||||||
<a class="esh-basketstatus @Model.Disabled"
|
<a class="esh-basketstatus @Model.Disabled"
|
||||||
asp-area=""
|
asp-area=""
|
||||||
asp-controller="Cart"
|
asp-controller="Cart"
|
||||||
asp-action="Index">
|
asp-action="Index">
|
||||||
<div class="esh-basketstatus-image">
|
|
||||||
<img src="~/images/cart.png" />
|
|
||||||
</div>
|
|
||||||
@if (ViewBag.IsBasketInoperative == true)
|
@if (ViewBag.IsBasketInoperative == true)
|
||||||
{
|
{
|
||||||
<div class="esh-basketstatus-badge-inoperative">
|
<div class="esh-basketstatus-image">
|
||||||
@Model.ItemsCount
|
<img src="~/images/cart-inoperative.png" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="esh-basketstatus-badge-inoperative">
|
||||||
|
X
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<div class="esh-basketstatus-image">
|
||||||
|
<img src="~/images/cart.png" />
|
||||||
|
</div>
|
||||||
<div class="esh-basketstatus-badge">
|
<div class="esh-basketstatus-badge">
|
||||||
@Model.ItemsCount
|
@Model.ItemsCount
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,67 +5,87 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<article class="esh-basket-titles row">
|
@if (TempData.ContainsKey("BasketInoperativeMsg"))
|
||||||
<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];
|
<br />
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
@TempData["BasketInoperativeMsg"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<article class="esh-basket-titles row">
|
||||||
|
<br />
|
||||||
|
@if (TempData.ContainsKey("BasketInoperativeMsg"))
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
@TempData["BasketInoperativeMsg"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<article class="esh-basket-items row">
|
<section class="esh-basket-title col-xs-3">Product</section>
|
||||||
<div>
|
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
|
||||||
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
|
<section class="esh-basket-title col-xs-2">Price</section>
|
||||||
<img class="esh-basket-image" src="@item.PictureUrl" />
|
<section class="esh-basket-title col-xs-2">Quantity</section>
|
||||||
</section>
|
<section class="esh-basket-title col-xs-2">Cost</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>
|
</article>
|
||||||
|
|
||||||
<div class="esh-basket-items--border row">
|
@for (int i = 0; i < Model.Items.Count; i++)
|
||||||
@if (item.OldUnitPrice != 0)
|
{
|
||||||
{
|
var item = Model.Items[i];
|
||||||
<div class="alert alert-warning esh-basket-margin12" role="alert"> 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">
|
||||||
|
<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"> 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>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
|
||||||
|
<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>
|
</div>
|
||||||
<br/>
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</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>
|
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" 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.AspNetCore.StaticFiles" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" />
|
||||||
|
BIN
src/Web/WebMVC/wwwroot/images/cart-inoperative.png
Normal file
BIN
src/Web/WebMVC/wwwroot/images/cart-inoperative.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 948 B |
Loading…
x
Reference in New Issue
Block a user