Created Retry and CircuitBreaker policies for MVC App
This commit is contained in:
parent
faf4ada8ac
commit
cb3f682872
@ -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 System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using Polly.CircuitBreaker;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
||||||
{
|
{
|
||||||
@ -37,18 +38,24 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Create(Order model, string action)
|
public async Task<IActionResult> Create(Order model, string action)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
try
|
||||||
{
|
{
|
||||||
var user = _appUserParser.Parse(HttpContext.User);
|
if (ModelState.IsValid)
|
||||||
await _orderSvc.CreateOrder(model);
|
{
|
||||||
|
var user = _appUserParser.Parse(HttpContext.User);
|
||||||
|
await _orderSvc.CreateOrder(model);
|
||||||
|
|
||||||
//Empty basket for current user.
|
//Empty basket for current user.
|
||||||
await _basketSvc.CleanBasket(user);
|
await _basketSvc.CleanBasket(user);
|
||||||
|
|
||||||
//Redirect to historic list.
|
//Redirect to historic list.
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(BrokenCircuitException ex)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError("Error", "Not possible to create a new order, please try later on");
|
||||||
}
|
}
|
||||||
|
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
using System;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using System.Net.Http;
|
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using System.Collections.Generic;
|
||||||
using Microsoft.eShopOnContainers.WebMVC.Extensions;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WebMVC.Services.Utilities;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||||
{
|
{
|
||||||
public class BasketService : IBasketService
|
public class BasketService : IBasketService
|
||||||
{
|
{
|
||||||
private readonly IOptionsSnapshot<AppSettings> _settings;
|
private readonly IOptionsSnapshot<AppSettings> _settings;
|
||||||
private HttpClient _apiClient;
|
private HttpClientWrapper _apiClient;
|
||||||
private readonly string _remoteServiceBaseUrl;
|
private readonly string _remoteServiceBaseUrl;
|
||||||
private IHttpContextAccessor _httpContextAccesor;
|
private IHttpContextAccessor _httpContextAccesor;
|
||||||
|
|
||||||
@ -31,8 +30,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
var context = _httpContextAccesor.HttpContext;
|
var context = _httpContextAccesor.HttpContext;
|
||||||
var token = await context.Authentication.GetTokenAsync("access_token");
|
var token = await context.Authentication.GetTokenAsync("access_token");
|
||||||
|
|
||||||
_apiClient = new HttpClient();
|
var _apiClient = new HttpClientWrapper();
|
||||||
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}";
|
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}";
|
||||||
var dataString = await _apiClient.GetStringAsync(basketUrl);
|
var dataString = await _apiClient.GetStringAsync(basketUrl);
|
||||||
@ -53,12 +52,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
var context = _httpContextAccesor.HttpContext;
|
var context = _httpContextAccesor.HttpContext;
|
||||||
var token = await context.Authentication.GetTokenAsync("access_token");
|
var token = await context.Authentication.GetTokenAsync("access_token");
|
||||||
|
|
||||||
_apiClient = new HttpClient();
|
_apiClient = new HttpClientWrapper();
|
||||||
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
var basketUrl = _remoteServiceBaseUrl;
|
var basketUrl = _remoteServiceBaseUrl;
|
||||||
StringContent content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
|
|
||||||
var response = await _apiClient.PostAsync(basketUrl, content);
|
var response = await _apiClient.PostAsync(basketUrl, basket);
|
||||||
|
|
||||||
return basket;
|
return basket;
|
||||||
}
|
}
|
||||||
@ -120,8 +119,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
var context = _httpContextAccesor.HttpContext;
|
var context = _httpContextAccesor.HttpContext;
|
||||||
var token = await context.Authentication.GetTokenAsync("access_token");
|
var token = await context.Authentication.GetTokenAsync("access_token");
|
||||||
|
|
||||||
_apiClient = new HttpClient();
|
_apiClient = new HttpClientWrapper();
|
||||||
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}";
|
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}";
|
||||||
var response = await _apiClient.DeleteAsync(basketUrl);
|
var response = await _apiClient.DeleteAsync(basketUrl);
|
||||||
|
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
using System;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
|
||||||
using Microsoft.CodeAnalysis.Options;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Net.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Microsoft.Extensions.Logging;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WebMVC.Services.Utilities;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||||
{
|
{
|
||||||
public class CatalogService : ICatalogService
|
public class CatalogService : ICatalogService
|
||||||
{
|
{
|
||||||
private readonly IOptionsSnapshot<AppSettings> _settings;
|
private readonly IOptionsSnapshot<AppSettings> _settings;
|
||||||
private HttpClient _apiClient;
|
private HttpClientWrapper _apiClient;
|
||||||
private readonly string _remoteServiceBaseUrl;
|
private readonly string _remoteServiceBaseUrl;
|
||||||
|
|
||||||
public CatalogService(IOptionsSnapshot<AppSettings> settings, ILoggerFactory loggerFactory) {
|
public CatalogService(IOptionsSnapshot<AppSettings> settings, ILoggerFactory loggerFactory) {
|
||||||
@ -29,7 +26,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
|
|
||||||
public async Task<Catalog> GetCatalogItems(int page,int take, int? brand, int? type)
|
public async Task<Catalog> GetCatalogItems(int page,int take, int? brand, int? type)
|
||||||
{
|
{
|
||||||
_apiClient = new HttpClient();
|
_apiClient = new HttpClientWrapper();
|
||||||
var itemsQs = $"items?pageIndex={page}&pageSize={take}";
|
var itemsQs = $"items?pageIndex={page}&pageSize={take}";
|
||||||
var filterQs = "";
|
var filterQs = "";
|
||||||
|
|
||||||
@ -45,16 +42,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
var dataString = "";
|
var dataString = "";
|
||||||
|
|
||||||
//
|
//
|
||||||
// Using HttpClient with Retry and Exponential Backoff
|
// Using a HttpClient wrapper with Retry and Exponential Backoff
|
||||||
//
|
//
|
||||||
var retry = new RetryWithExponentialBackoff();
|
dataString = await _apiClient.GetStringAsync(catalogUrl);
|
||||||
await retry.RunAsync(async () =>
|
|
||||||
{
|
|
||||||
// work with HttpClient call
|
|
||||||
dataString = await _apiClient.GetStringAsync(catalogUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
//var dataString = await _apiClient.GetStringAsync(catalogUrl);
|
|
||||||
var response = JsonConvert.DeserializeObject<Catalog>(dataString);
|
var response = JsonConvert.DeserializeObject<Catalog>(dataString);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@ -62,7 +53,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
|
|
||||||
public async Task<IEnumerable<SelectListItem>> GetBrands()
|
public async Task<IEnumerable<SelectListItem>> GetBrands()
|
||||||
{
|
{
|
||||||
_apiClient = new HttpClient();
|
_apiClient = new HttpClientWrapper();
|
||||||
var url = $"{_remoteServiceBaseUrl}catalogBrands";
|
var url = $"{_remoteServiceBaseUrl}catalogBrands";
|
||||||
var dataString = await _apiClient.GetStringAsync(url);
|
var dataString = await _apiClient.GetStringAsync(url);
|
||||||
|
|
||||||
@ -81,7 +72,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
|
|
||||||
public async Task<IEnumerable<SelectListItem>> GetTypes()
|
public async Task<IEnumerable<SelectListItem>> GetTypes()
|
||||||
{
|
{
|
||||||
_apiClient = new HttpClient();
|
_apiClient = new HttpClientWrapper();
|
||||||
var url = $"{_remoteServiceBaseUrl}catalogTypes";
|
var url = $"{_remoteServiceBaseUrl}catalogTypes";
|
||||||
var dataString = await _apiClient.GetStringAsync(url);
|
var dataString = await _apiClient.GetStringAsync(url);
|
||||||
|
|
||||||
|
@ -8,12 +8,13 @@ using Microsoft.Extensions.Options;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using WebMVC.Services.Utilities;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||||
{
|
{
|
||||||
public class OrderingService : IOrderingService
|
public class OrderingService : IOrderingService
|
||||||
{
|
{
|
||||||
private HttpClient _apiClient;
|
private HttpClientWrapper _apiClient;
|
||||||
private readonly string _remoteServiceBaseUrl;
|
private readonly string _remoteServiceBaseUrl;
|
||||||
private readonly IOptionsSnapshot<AppSettings> _settings;
|
private readonly IOptionsSnapshot<AppSettings> _settings;
|
||||||
private readonly IHttpContextAccessor _httpContextAccesor;
|
private readonly IHttpContextAccessor _httpContextAccesor;
|
||||||
@ -30,8 +31,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
var context = _httpContextAccesor.HttpContext;
|
var context = _httpContextAccesor.HttpContext;
|
||||||
var token = await context.Authentication.GetTokenAsync("access_token");
|
var token = await context.Authentication.GetTokenAsync("access_token");
|
||||||
|
|
||||||
_apiClient = new HttpClient();
|
_apiClient = new HttpClientWrapper();
|
||||||
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
var ordersUrl = $"{_remoteServiceBaseUrl}/{Id}";
|
var ordersUrl = $"{_remoteServiceBaseUrl}/{Id}";
|
||||||
var dataString = await _apiClient.GetStringAsync(ordersUrl);
|
var dataString = await _apiClient.GetStringAsync(ordersUrl);
|
||||||
@ -46,8 +47,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
var context = _httpContextAccesor.HttpContext;
|
var context = _httpContextAccesor.HttpContext;
|
||||||
var token = await context.Authentication.GetTokenAsync("access_token");
|
var token = await context.Authentication.GetTokenAsync("access_token");
|
||||||
|
|
||||||
_apiClient = new HttpClient();
|
_apiClient = new HttpClientWrapper();
|
||||||
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
var ordersUrl = _remoteServiceBaseUrl;
|
var ordersUrl = _remoteServiceBaseUrl;
|
||||||
var dataString = await _apiClient.GetStringAsync(ordersUrl);
|
var dataString = await _apiClient.GetStringAsync(ordersUrl);
|
||||||
@ -77,17 +78,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
var context = _httpContextAccesor.HttpContext;
|
var context = _httpContextAccesor.HttpContext;
|
||||||
var token = await context.Authentication.GetTokenAsync("access_token");
|
var token = await context.Authentication.GetTokenAsync("access_token");
|
||||||
|
|
||||||
_apiClient = new HttpClient();
|
_apiClient = new HttpClientWrapper();
|
||||||
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
_apiClient.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString());
|
_apiClient.Inst.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString());
|
||||||
|
|
||||||
var ordersUrl = $"{_remoteServiceBaseUrl}/new";
|
var ordersUrl = $"{_remoteServiceBaseUrl}/new";
|
||||||
order.CardTypeId = 1;
|
order.CardTypeId = 1;
|
||||||
order.CardExpirationApiFormat();
|
order.CardExpirationApiFormat();
|
||||||
SetFakeIdToProducts(order);
|
SetFakeIdToProducts(order);
|
||||||
|
|
||||||
StringContent content = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
|
var response = await _apiClient.PostAsync(ordersUrl, order);
|
||||||
|
|
||||||
var response = await _apiClient.PostAsync(ordersUrl, content);
|
|
||||||
|
|
||||||
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
|
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
|
||||||
throw new Exception("Error creating order, try later");
|
throw new Exception("Error creating order, try later");
|
||||||
|
99
src/Web/WebMVC/Services/Utilities/HttpClientWrapper.cs
Normal file
99
src/Web/WebMVC/Services/Utilities/HttpClientWrapper.cs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Polly;
|
||||||
|
using Polly.Wrap;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WebMVC.Services.Utilities
|
||||||
|
{
|
||||||
|
public class HttpClientWrapper
|
||||||
|
{
|
||||||
|
private HttpClient _client;
|
||||||
|
private PolicyWrap _policyWrapper;
|
||||||
|
private ILogger _logger;
|
||||||
|
public HttpClient Inst => _client;
|
||||||
|
public HttpClientWrapper()
|
||||||
|
{
|
||||||
|
_client = new HttpClient();
|
||||||
|
_logger = new LoggerFactory().CreateLogger(nameof(HttpClientWrapper));
|
||||||
|
|
||||||
|
// Add Policies to be applied
|
||||||
|
_policyWrapper = Policy.WrapAsync(
|
||||||
|
CreateRetryPolicy(),
|
||||||
|
CreateCircuitBreakerPolicy()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Policy CreateCircuitBreakerPolicy()
|
||||||
|
{
|
||||||
|
return Policy
|
||||||
|
.Handle<HttpRequestException>()
|
||||||
|
.CircuitBreakerAsync(
|
||||||
|
// number of exceptions before breaking circuit
|
||||||
|
3,
|
||||||
|
// time circuit opened before retry
|
||||||
|
TimeSpan.FromMinutes(1),
|
||||||
|
(exception, duration) => {
|
||||||
|
// on circuit opened
|
||||||
|
_logger.LogTrace("Circuit breaker opened");
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// on circuit closed
|
||||||
|
_logger.LogTrace("Circuit breaker reset");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Policy CreateRetryPolicy()
|
||||||
|
{
|
||||||
|
return Policy
|
||||||
|
.Handle<HttpRequestException>()
|
||||||
|
.WaitAndRetryAsync(
|
||||||
|
// number of retries
|
||||||
|
3,
|
||||||
|
// exponential backofff
|
||||||
|
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
||||||
|
// on retry
|
||||||
|
(exception, timeSpan, retryCount, context) =>
|
||||||
|
{
|
||||||
|
_logger.LogTrace($"Retry {retryCount} " +
|
||||||
|
$"of {context.PolicyKey} " +
|
||||||
|
$"at {context.ExecutionKey}, " +
|
||||||
|
$"due to: {exception}.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetStringAsync(string uri)
|
||||||
|
{
|
||||||
|
return await HttpInvoker(async () =>
|
||||||
|
await _client.GetStringAsync(uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponseMessage> PostAsync<T>(string uri, T item)
|
||||||
|
{
|
||||||
|
// a new StringContent must be created for each retry
|
||||||
|
// as it is disposed after each call
|
||||||
|
return await HttpInvoker(async () =>
|
||||||
|
await _client.PostAsync(uri,
|
||||||
|
new StringContent(JsonConvert.SerializeObject(item),
|
||||||
|
System.Text.Encoding.UTF8, "application/json")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<HttpResponseMessage> DeleteAsync(string uri)
|
||||||
|
{
|
||||||
|
return await HttpInvoker(async () =>
|
||||||
|
await _client.DeleteAsync(uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<T> HttpInvoker<T>(Func<Task<T>> action)
|
||||||
|
{
|
||||||
|
// Executes the action applying all
|
||||||
|
// the policies defined in the wrapper
|
||||||
|
return await _policyWrapper
|
||||||
|
.ExecuteAsync(async () => await action());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,90 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// When working with cloud services and Docker containers, it's very important to always catch
|
|
||||||
/// TimeoutException, and retry the operation.
|
|
||||||
/// RetryWithExponentialBackoff makes it easy to implement such pattern.
|
|
||||||
/// Usage:
|
|
||||||
/// var retry = new RetryWithExponentialBackoff();
|
|
||||||
/// await retry.RunAsync(async ()=>
|
|
||||||
/// {
|
|
||||||
/// // work with HttpClient
|
|
||||||
/// });
|
|
||||||
/// </summary>
|
|
||||||
public sealed class RetryWithExponentialBackoff
|
|
||||||
{
|
|
||||||
private readonly int maxRetries, delayMilliseconds, maxDelayMilliseconds;
|
|
||||||
|
|
||||||
public RetryWithExponentialBackoff(int maxRetries = 50, int delayMilliseconds = 200, int maxDelayMilliseconds = 2000)
|
|
||||||
{
|
|
||||||
this.maxRetries = maxRetries;
|
|
||||||
this.delayMilliseconds = delayMilliseconds;
|
|
||||||
this.maxDelayMilliseconds = maxDelayMilliseconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RunAsync(Func<Task> func)
|
|
||||||
{
|
|
||||||
ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries, this.delayMilliseconds, this.maxDelayMilliseconds);
|
|
||||||
retry:
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await func();
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (ex is TimeoutException || ex is System.Net.Http.HttpRequestException)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("Exception raised is: " + ex.GetType().ToString() + " -- Message: " + ex.Message + " -- Inner Message: " + ex.InnerException.Message);
|
|
||||||
await backoff.Delay();
|
|
||||||
goto retry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Usage:
|
|
||||||
/// ExponentialBackoff backoff = new ExponentialBackoff(3, 10, 100);
|
|
||||||
/// retry:
|
|
||||||
/// try {
|
|
||||||
/// // ...
|
|
||||||
/// }
|
|
||||||
/// catch (Exception ex) {
|
|
||||||
/// await backoff.Delay(cancellationToken);
|
|
||||||
/// goto retry;
|
|
||||||
/// }
|
|
||||||
/// </summary>
|
|
||||||
public struct ExponentialBackoff
|
|
||||||
{
|
|
||||||
private readonly int m_maxRetries, m_delayMilliseconds, m_maxDelayMilliseconds;
|
|
||||||
private int m_retries, m_pow;
|
|
||||||
|
|
||||||
public ExponentialBackoff(int maxRetries, int delayMilliseconds, int maxDelayMilliseconds)
|
|
||||||
{
|
|
||||||
m_maxRetries = maxRetries;
|
|
||||||
m_delayMilliseconds = delayMilliseconds;
|
|
||||||
m_maxDelayMilliseconds = maxDelayMilliseconds;
|
|
||||||
m_retries = 0;
|
|
||||||
m_pow = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Delay()
|
|
||||||
{
|
|
||||||
if (m_retries == m_maxRetries)
|
|
||||||
{
|
|
||||||
throw new TimeoutException("Max retry attempts exceeded.");
|
|
||||||
}
|
|
||||||
++m_retries;
|
|
||||||
if (m_retries < 31)
|
|
||||||
{
|
|
||||||
m_pow = m_pow << 1; // m_pow = Pow(2, m_retries - 1)
|
|
||||||
}
|
|
||||||
int delay = Math.Min(m_delayMilliseconds * (m_pow - 1) / 2, m_maxDelayMilliseconds);
|
|
||||||
return Task.Delay(delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,6 +11,13 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<form method="post" asp-controller="Order" asp-action="Create">
|
<form method="post" asp-controller="Order" asp-action="Create">
|
||||||
<section class="esh-orders_new-section">
|
<section class="esh-orders_new-section">
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var error in ViewData.ModelState.Values.SelectMany(err => err.Errors)) {
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
@error.ErrorMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<h4 class="esh-orders_new-title">Shipping address</h4>
|
<h4 class="esh-orders_new-title">Shipping address</h4>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
<PrivateAssets>All</PrivateAssets>
|
<PrivateAssets>All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||||
|
<PackageReference Include="Polly" Version="5.0.6" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.1.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.0" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user