Skip to content

Commit d42033a

Browse files
committed
Added UI alert and circuit-breaker handlers for components that depend on Basket.api
1 parent f39fdb9 commit d42033a

11 files changed

Lines changed: 176 additions & 91 deletions

File tree

src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ public Task<string> GetStringAsync(string uri, string authorizationToken = null,
8181

8282
var response = await _client.SendAsync(requestMessage);
8383

84+
// raise exception if HttpResponseCode 500
85+
// needed for circuit breaker to track fails
86+
87+
if (response.StatusCode == HttpStatusCode.InternalServerError)
88+
{
89+
throw new HttpRequestException();
90+
}
91+
8492
return await response.Content.ReadAsStringAsync();
8593
});
8694
}

src/Services/Basket/Basket.API/Program.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ public static void Main(string[] args)
1414
.UseKestrel()
1515
.UseFailing(options =>
1616
{
17-
options.ConfigPath = "/Failing";
18-
options.EndpointPaths = new List<string>()
19-
{ "/api/v1/basket/checkout", "/hc" };
17+
options.ConfigPath = "/Failing";
2018
})
2119
.UseHealthChecks("/hc")
2220
.UseContentRoot(Directory.GetCurrentDirectory())

src/Web/WebMVC/Controllers/CartController.cs

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
88
using Microsoft.AspNetCore.Authorization;
99
using Microsoft.AspNetCore.Authentication;
10+
using Polly.CircuitBreaker;
1011

1112
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
1213
{
@@ -26,47 +27,79 @@ public CartController(IBasketService basketSvc, ICatalogService catalogSvc, IIde
2627

2728
public async Task<IActionResult> Index()
2829
{
29-
var user = _appUserParser.Parse(HttpContext.User);
30-
var vm = await _basketSvc.GetBasket(user);
31-
30+
try
31+
{
32+
var user = _appUserParser.Parse(HttpContext.User);
33+
var vm = await _basketSvc.GetBasket(user);
3234

33-
return View(vm);
35+
return View(vm);
36+
}
37+
catch (BrokenCircuitException)
38+
{
39+
// Catch error when Basket.api is in circuit-opened mode
40+
HandleBrokenCircuitException();
41+
}
42+
43+
return View();
3444
}
3545

3646

3747
[HttpPost]
3848
public async Task<IActionResult> Index(Dictionary<string, int> quantities, string action)
3949
{
40-
var user = _appUserParser.Parse(HttpContext.User);
41-
var basket = await _basketSvc.SetQuantities(user, quantities);
42-
var vm = await _basketSvc.UpdateBasket(basket);
50+
try
51+
{
52+
var user = _appUserParser.Parse(HttpContext.User);
53+
var basket = await _basketSvc.SetQuantities(user, quantities);
54+
var vm = await _basketSvc.UpdateBasket(basket);
4355

44-
if (action == "[ Checkout ]")
56+
if (action == "[ Checkout ]")
57+
{
58+
var order = _basketSvc.MapBasketToOrder(basket);
59+
return RedirectToAction("Create", "Order");
60+
}
61+
}
62+
catch (BrokenCircuitException)
4563
{
46-
var order = _basketSvc.MapBasketToOrder(basket);
47-
return RedirectToAction("Create", "Order");
64+
// Catch error when Basket.api is in circuit-opened mode
65+
HandleBrokenCircuitException();
4866
}
49-
50-
return View(vm);
67+
68+
return View();
5169
}
5270

5371
public async Task<IActionResult> AddToCart(CatalogItem productDetails)
5472
{
55-
if (productDetails.Id != null)
73+
try
5674
{
57-
var user = _appUserParser.Parse(HttpContext.User);
58-
var product = new BasketItem()
75+
if (productDetails.Id != null)
5976
{
60-
Id = Guid.NewGuid().ToString(),
61-
Quantity = 1,
62-
ProductName = productDetails.Name,
63-
PictureUrl = productDetails.PictureUri,
64-
UnitPrice = productDetails.Price,
65-
ProductId = productDetails.Id
66-
};
67-
await _basketSvc.AddItemToBasket(user, product);
77+
var user = _appUserParser.Parse(HttpContext.User);
78+
var product = new BasketItem()
79+
{
80+
Id = Guid.NewGuid().ToString(),
81+
Quantity = 1,
82+
ProductName = productDetails.Name,
83+
PictureUrl = productDetails.PictureUri,
84+
UnitPrice = productDetails.Price,
85+
ProductId = productDetails.Id
86+
};
87+
await _basketSvc.AddItemToBasket(user, product);
88+
}
89+
return RedirectToAction("Index", "Catalog");
90+
}
91+
catch (BrokenCircuitException)
92+
{
93+
// Catch error when Basket.api is in circuit-opened mode
94+
HandleBrokenCircuitException();
6895
}
96+
6997
return RedirectToAction("Index", "Catalog");
7098
}
99+
100+
private void HandleBrokenCircuitException()
101+
{
102+
TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business Msg Due to Circuit-Breaker)";
103+
}
71104
}
72105
}

src/Web/WebMVC/Startup.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public Startup(IHostingEnvironment env)
4040
// This method gets called by the runtime. Use this method to add services to the container.
4141
public void ConfigureServices(IServiceCollection services)
4242
{
43-
services.AddMvc();
43+
services.AddMvc();
44+
services.AddSession();
4445

4546
if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString)
4647
{
@@ -104,6 +105,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
104105
app.UseExceptionHandler("/Catalog/Error");
105106
}
106107

108+
app.UseSession();
107109
app.UseStaticFiles();
108110

109111
app.UseCookieAuthentication(new CookieAuthenticationOptions

src/Web/WebMVC/ViewComponents/Cart.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public async Task<IViewComponentResult> InvokeAsync(ApplicationUser user)
2929
{
3030
// Catch error when Basket.api is in circuit-opened mode
3131
ViewBag.IsBasketInoperative = true;
32-
vm.ItemsCount = 0;
3332
}
3433

3534
return View(vm);

src/Web/WebMVC/ViewComponents/CartList.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Threading.Tasks;
8+
using Polly.CircuitBreaker;
89

910
namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents
1011
{
@@ -16,8 +17,19 @@ public class CartList : ViewComponent
1617

1718
public async Task<IViewComponentResult> InvokeAsync(ApplicationUser user)
1819
{
19-
var item = await GetItemsAsync(user);
20-
return View(item);
20+
var vm = new Basket();
21+
try
22+
{
23+
vm = await GetItemsAsync(user);
24+
return View(vm);
25+
}
26+
catch (BrokenCircuitException)
27+
{
28+
// Catch error when Basket.api is in circuit-opened mode
29+
TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business Msg Due to Circuit-Breaker)";
30+
}
31+
32+
return View(vm);
2133
}
2234

2335
private Task<Basket> GetItemsAsync(ApplicationUser user) => _cartSvc.GetBasket(user);

src/Web/WebMVC/Views/Catalog/Index.cshtml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@
2323
</section>
2424

2525
<div class="container">
26+
<div class="row">
27+
<br />
28+
@if(TempData.ContainsKey("BasketInoperativeMsg"))
29+
{
30+
<div class="alert alert-warning" role="alert">
31+
&nbsp;@TempData["BasketInoperativeMsg"]
32+
</div>
33+
}
34+
</div>
2635

2736
@if (Model.CatalogItems.Count() > 0)
2837
{

src/Web/WebMVC/Views/Shared/Components/Cart/Default.cshtml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@
77
<a class="esh-basketstatus @Model.Disabled"
88
asp-area=""
99
asp-controller="Cart"
10-
asp-action="Index">
11-
<div class="esh-basketstatus-image">
12-
<img src="~/images/cart.png" />
13-
</div>
10+
asp-action="Index">
1411
@if (ViewBag.IsBasketInoperative == true)
1512
{
16-
<div class="esh-basketstatus-badge-inoperative">
17-
@Model.ItemsCount
13+
<div class="esh-basketstatus-image">
14+
<img src="~/images/cart-inoperative.png" />
1815
</div>
16+
<div class="esh-basketstatus-badge-inoperative">
17+
X
18+
</div>
1919
}
2020
else
2121
{
22+
<div class="esh-basketstatus-image">
23+
<img src="~/images/cart.png" />
24+
</div>
2225
<div class="esh-basketstatus-badge">
2326
@Model.ItemsCount
2427
</div>

src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml

Lines changed: 75 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,67 +5,87 @@
55
}
66

77
<div class="container">
8-
<article class="esh-basket-titles row">
9-
<section class="esh-basket-title col-xs-3">Product</section>
10-
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
11-
<section class="esh-basket-title col-xs-2">Price</section>
12-
<section class="esh-basket-title col-xs-2">Quantity</section>
13-
<section class="esh-basket-title col-xs-2">Cost</section>
14-
</article>
15-
16-
@for (int i = 0; i < Model.Items.Count; i++)
8+
@if (TempData.ContainsKey("BasketInoperativeMsg"))
9+
{
10+
<br />
11+
<div class="alert alert-warning" role="alert">
12+
&nbsp;@TempData["BasketInoperativeMsg"]
13+
</div>
14+
}
15+
else
1716
{
18-
var item = Model.Items[i];
17+
<article class="esh-basket-titles row">
18+
<br />
19+
@if (TempData.ContainsKey("BasketInoperativeMsg"))
20+
{
21+
<div class="alert alert-warning" role="alert">
22+
&nbsp;@TempData["BasketInoperativeMsg"]
23+
</div>
24+
}
1925

20-
<article class="esh-basket-items row">
21-
<div>
22-
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
23-
<img class="esh-basket-image" src="@item.PictureUrl" />
24-
</section>
25-
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section>
26-
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice.ToString("N2")</section>
27-
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
28-
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
29-
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
30-
</section>
31-
<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>
26+
<section class="esh-basket-title col-xs-3">Product</section>
27+
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
28+
<section class="esh-basket-title col-xs-2">Price</section>
29+
<section class="esh-basket-title col-xs-2">Quantity</section>
30+
<section class="esh-basket-title col-xs-2">Cost</section>
31+
</article>
32+
33+
@for (int i = 0; i < Model.Items.Count; i++)
34+
{
35+
var item = Model.Items[i];
36+
37+
<article class="esh-basket-items row">
38+
<div>
39+
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
40+
<img class="esh-basket-image" src="@item.PictureUrl" />
41+
</section>
42+
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section>
43+
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice.ToString("N2")</section>
44+
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
45+
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
46+
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
47+
</section>
48+
<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>
49+
</div>
50+
<div class="row">
51+
52+
</div>
53+
</article>
54+
55+
<div class="esh-basket-items--border row">
56+
@if (item.OldUnitPrice != 0)
57+
{
58+
<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>
59+
}
3260
</div>
33-
<div class="row">
61+
<br />
62+
}
3463

35-
</div>
36-
</article>
64+
<div class="container">
65+
<article class="esh-basket-titles esh-basket-titles--clean row">
66+
<section class="esh-basket-title col-xs-10"></section>
67+
<section class="esh-basket-title col-xs-2">Total</section>
68+
</article>
3769

38-
<div class="esh-basket-items--border row">
39-
@if (item.OldUnitPrice != 0)
40-
{
41-
<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>
42-
}
70+
<article class="esh-basket-items row">
71+
<section class="esh-basket-item col-xs-10"></section>
72+
<section class="esh-basket-item esh-basket-item--mark col-xs-2">$ @Model.Total()</section>
73+
</article>
74+
75+
<article class="esh-basket-items row">
76+
<section class="esh-basket-item col-xs-7"></section>
77+
<section class="esh-basket-item col-xs-2">
78+
<button class="btn esh-basket-checkout" name="name" value="" type="submit">[ Update ]</button>
79+
</section>
80+
<section class="esh-basket-item col-xs-3">
81+
<input type="submit"
82+
class="btn esh-basket-checkout"
83+
value="[ Checkout ]" name="action" />
84+
</section>
85+
</article>
4386
</div>
44-
<br/>
45-
4687
}
88+
4789
</div>
4890

49-
<div class="container">
50-
<article class="esh-basket-titles esh-basket-titles--clean row">
51-
<section class="esh-basket-title col-xs-10"></section>
52-
<section class="esh-basket-title col-xs-2">Total</section>
53-
</article>
54-
55-
<article class="esh-basket-items row">
56-
<section class="esh-basket-item col-xs-10"></section>
57-
<section class="esh-basket-item esh-basket-item--mark col-xs-2">$ @Model.Total()</section>
58-
</article>
59-
60-
<article class="esh-basket-items row">
61-
<section class="esh-basket-item col-xs-7"></section>
62-
<section class="esh-basket-item col-xs-2">
63-
<button class="btn esh-basket-checkout" name="name" value="" type="submit">[ Update ]</button>
64-
</section>
65-
<section class="esh-basket-item col-xs-3">
66-
<input type="submit"
67-
class="btn esh-basket-checkout"
68-
value="[ Checkout ]" name="action" />
69-
</section>
70-
</article>
71-
</div>
91+

src/Web/WebMVC/WebMVC.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
4242
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.2" />
4343
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.2" />
44+
<PackageReference Include="Microsoft.AspNetCore.Session" Version="1.1.2" />
4445
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" />
4546
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
4647
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" />

0 commit comments

Comments
 (0)