Added HTTPClient services and review application on WebMVC

This commit is contained in:
Unai Zorrilla Castro 2018-05-18 12:36:33 +02:00
parent dc5de83747
commit 2f9fa4dcca
11 changed files with 221 additions and 307 deletions

View File

@ -36,7 +36,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
var basket = await _repository.GetBasketAsync(id);
if (basket == null)
{
return NotFound();
return Ok(new CustomerBasket(id) { });
}
return Ok(basket);

View File

@ -7,9 +7,8 @@ namespace Microsoft.eShopOnContainers.WebMVC
{
public class AppSettings
{
public Connectionstrings ConnectionStrings { get; set; }
//public Connectionstrings ConnectionStrings { get; set; }
public string MarketingUrl { get; set; }
public string PurchaseUrl { get; set; }
public string SignalrHubUrl { get; set; }
public bool ActivateCampaignDetailFunction { get; set; }

View File

@ -0,0 +1,26 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace WebMVC.Infrastructure
{
public class HttpClientRequestIdDelegatingHandler
: DelegatingHandler
{
public HttpClientRequestIdDelegatingHandler()
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Post || request.Method == HttpMethod.Put)
{
request.Headers.Add("x-requestid", Guid.NewGuid().ToString());
}
return await base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -3,8 +3,8 @@ using System;
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
{
public interface IResilientHttpClientFactory
{
ResilientHttpClient CreateResilientHttpClient();
}
//public interface IResilientHttpClientFactory
//{
// ResilientHttpClient CreateResilientHttpClient();
//}
}

View File

@ -7,60 +7,60 @@ using System.Net.Http;
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
{
public class ResilientHttpClientFactory : IResilientHttpClientFactory
{
private readonly ILogger<ResilientHttpClient> _logger;
private readonly int _retryCount;
private readonly int _exceptionsAllowedBeforeBreaking;
private readonly IHttpContextAccessor _httpContextAccessor;
//public class ResilientHttpClientFactory : IResilientHttpClientFactory
//{
// private readonly ILogger<ResilientHttpClient> _logger;
// private readonly int _retryCount;
// private readonly int _exceptionsAllowedBeforeBreaking;
// private readonly IHttpContextAccessor _httpContextAccessor;
public ResilientHttpClientFactory(ILogger<ResilientHttpClient> logger, IHttpContextAccessor httpContextAccessor, int exceptionsAllowedBeforeBreaking = 5, int retryCount = 6)
{
_logger = logger;
_exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
_retryCount = retryCount;
_httpContextAccessor = httpContextAccessor;
}
// public ResilientHttpClientFactory(ILogger<ResilientHttpClient> logger, IHttpContextAccessor httpContextAccessor, int exceptionsAllowedBeforeBreaking = 5, int retryCount = 6)
// {
// _logger = logger;
// _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
// _retryCount = retryCount;
// _httpContextAccessor = httpContextAccessor;
// }
public ResilientHttpClient CreateResilientHttpClient()
=> new ResilientHttpClient((origin) => CreatePolicies(), _logger, _httpContextAccessor);
// public ResilientHttpClient CreateResilientHttpClient()
// => new ResilientHttpClient((origin) => CreatePolicies(), _logger, _httpContextAccessor);
private Policy[] CreatePolicies()
=> new Policy[]
{
Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(
// number of retries
_retryCount,
// exponential backofff
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
// on retry
(exception, timeSpan, retryCount, context) =>
{
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
$"of {context.PolicyKey} " +
$"at {context.OperationKey}, " +
$"due to: {exception}.";
_logger.LogWarning(msg);
_logger.LogDebug(msg);
}),
Policy.Handle<HttpRequestException>()
.CircuitBreakerAsync(
// number of exceptions before breaking circuit
_exceptionsAllowedBeforeBreaking,
// 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[] CreatePolicies()
// => new Policy[]
// {
// Policy.Handle<HttpRequestException>()
// .WaitAndRetryAsync(
// // number of retries
// _retryCount,
// // exponential backofff
// retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
// // on retry
// (exception, timeSpan, retryCount, context) =>
// {
// var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
// $"of {context.PolicyKey} " +
// $"at {context.OperationKey}, " +
// $"due to: {exception}.";
// _logger.LogWarning(msg);
// _logger.LogDebug(msg);
// }),
// Policy.Handle<HttpRequestException>()
// .CircuitBreakerAsync(
// // number of exceptions before breaking circuit
// _exceptionsAllowedBeforeBreaking,
// // 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");
// })
// };
//}
}

View File

@ -12,14 +12,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public class BasketService : IBasketService
{
private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IOptions<AppSettings> _settings;
private readonly HttpClient _apiClient;
private readonly string _basketByPassUrl;
private readonly string _purchaseUrl;
private readonly string _bffUrl;
public BasketService(HttpClient httpClient,IOptionsSnapshot<AppSettings> settings)
public BasketService(HttpClient httpClient, IOptions<AppSettings> settings)
{
_apiClient = httpClient;
_settings = settings;
@ -30,21 +30,22 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<Basket> GetBasket(ApplicationUser user)
{
var getBasketUri = API.Basket.GetBasket(_basketByPassUrl, user.Id);
var uri = API.Basket.GetBasket(_basketByPassUrl, user.Id);
var dataString = await _apiClient.GetStringAsync(getBasketUri);
var responseString = await _apiClient.GetStringAsync(uri);
return string.IsNullOrEmpty(dataString) ?
new Basket() { BuyerId = user.Id} :
JsonConvert.DeserializeObject<Basket>(dataString);
return string.IsNullOrEmpty(responseString) ?
new Basket() { BuyerId = user.Id } :
JsonConvert.DeserializeObject<Basket>(responseString);
}
public async Task<Basket> UpdateBasket(Basket basket)
{
var updateBasketUri = API.Basket.UpdateBasket(_basketByPassUrl);
var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var uri = API.Basket.UpdateBasket(_basketByPassUrl);
var response = await _apiClient.PostAsync(updateBasketUri, content);
var basketContent = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(uri, basketContent);
response.EnsureSuccessStatusCode();
@ -53,18 +54,18 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task Checkout(BasketDTO basket)
{
var updateBasketUri = API.Basket.CheckoutBasket(_basketByPassUrl);
var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var uri = API.Basket.CheckoutBasket(_basketByPassUrl);
var basketContent = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(updateBasketUri, content);
var response = await _apiClient.PostAsync(uri, basketContent);
response.EnsureSuccessStatusCode();
}
public async Task<Basket> SetQuantities(ApplicationUser user, Dictionary<string, int> quantities)
{
var uri = API.Purchase.UpdateBasketItem(_purchaseUrl);
var updateBasketUri = API.Purchase.UpdateBasketItem(_purchaseUrl);
var basketUpdate = new
{
BasketId = user.Id,
@ -75,9 +76,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
}).ToArray()
};
var content = new StringContent(JsonConvert.SerializeObject(basketUpdate), System.Text.Encoding.UTF8, "application/json");
var basketContent = new StringContent(JsonConvert.SerializeObject(basketUpdate), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PutAsync(updateBasketUri,content);
var response = await _apiClient.PutAsync(uri, basketContent);
response.EnsureSuccessStatusCode();
@ -88,17 +89,18 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<Order> GetOrderDraft(string basketId)
{
var draftOrderUri = API.Purchase.GetOrderDraft(_purchaseUrl, basketId);
var response = await _apiClient.GetStringAsync(draftOrderUri);
var uri = API.Purchase.GetOrderDraft(_purchaseUrl, basketId);
return JsonConvert.DeserializeObject<Order>(response);
var responseString = await _apiClient.GetStringAsync(uri);
var response = JsonConvert.DeserializeObject<Order>(responseString);
return response;
}
public async Task AddItemToBasket(ApplicationUser user, int productId)
{
var updateBasketUri = API.Purchase.AddItemToBasket(_purchaseUrl);
var uri = API.Purchase.AddItemToBasket(_purchaseUrl);
var newItem = new
{
@ -107,9 +109,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
Quantity = 1
};
var content = new StringContent(JsonConvert.SerializeObject(newItem), System.Text.Encoding.UTF8, "application/json");
var basketContent = new StringContent(JsonConvert.SerializeObject(newItem), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(updateBasketUri, content);
var response = await _apiClient.PostAsync(uri, basketContent);
}
}
}

View File

@ -1,69 +1,49 @@
namespace Microsoft.eShopOnContainers.WebMVC.Services
{
using global::WebMVC.Infrastructure;
using AspNetCore.Authentication;
using AspNetCore.Http;
using BuildingBlocks.Resilience.Http;
using ViewModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using ViewModels;
public class CampaignService : ICampaignService
{
private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IHttpClient _apiClient;
private readonly IOptions<AppSettings> _settings;
private readonly HttpClient _httpClient;
private readonly ILogger<CampaignService> _logger;
private readonly string _remoteServiceBaseUrl;
private readonly IHttpContextAccessor _httpContextAccesor;
public CampaignService(IOptionsSnapshot<AppSettings> settings, IHttpClient httpClient,
ILogger<CampaignService> logger, IHttpContextAccessor httpContextAccesor)
public CampaignService(IOptions<AppSettings> settings, HttpClient httpClient, ILogger<CampaignService> logger)
{
_settings = settings;
_apiClient = httpClient;
_httpClient = httpClient;
_logger = logger;
_remoteServiceBaseUrl = $"{_settings.Value.MarketingUrl}/api/v1/m/campaigns/";
_httpContextAccesor = httpContextAccesor ?? throw new ArgumentNullException(nameof(httpContextAccesor));
}
public async Task<Campaign> GetCampaigns(int pageSize, int pageIndex)
{
var allCampaignItemsUri = API.Marketing.GetAllCampaigns(_remoteServiceBaseUrl,
pageSize, pageIndex);
var uri = API.Marketing.GetAllCampaigns(_remoteServiceBaseUrl, pageSize, pageIndex);
var authorizationToken = await GetUserTokenAsync();
var dataString = await _apiClient.GetStringAsync(allCampaignItemsUri, authorizationToken);
var responseString = await _httpClient.GetStringAsync(uri);
var response = JsonConvert.DeserializeObject<Campaign>(dataString);
var response = JsonConvert.DeserializeObject<Campaign>(responseString);
return response;
}
public async Task<CampaignItem> GetCampaignById(int id)
{
var campaignByIdItemUri = API.Marketing.GetAllCampaignById(_remoteServiceBaseUrl, id);
var uri = API.Marketing.GetAllCampaignById(_remoteServiceBaseUrl, id);
var authorizationToken = await GetUserTokenAsync();
var dataString = await _apiClient.GetStringAsync(campaignByIdItemUri, authorizationToken);
var responseString = await _httpClient.GetStringAsync(uri);
var response = JsonConvert.DeserializeObject<CampaignItem>(dataString);
var response = JsonConvert.DeserializeObject<CampaignItem>(responseString);
return response;
}
private string GetUserIdentity()
{
return _httpContextAccesor.HttpContext.User.FindFirst("sub").Value;
}
private async Task<string> GetUserTokenAsync()
{
var context = _httpContextAccesor.HttpContext;
return await context.GetTokenAsync("access_token");
}
}
}

View File

@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using WebMVC.Infrastructure;
@ -13,16 +13,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public class CatalogService : ICatalogService
{
private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IHttpClient _apiClient;
private readonly IOptions<AppSettings> _settings;
private readonly HttpClient _httpClient;
private readonly ILogger<CatalogService> _logger;
private readonly string _remoteServiceBaseUrl;
public CatalogService(IOptionsSnapshot<AppSettings> settings, IHttpClient httpClient, ILogger<CatalogService> logger)
public CatalogService(HttpClient httpClient, ILogger<CatalogService> logger, IOptions<AppSettings> settings)
{
_httpClient = httpClient;
_settings = settings;
_apiClient = httpClient;
_logger = logger;
_remoteServiceBaseUrl = $"{_settings.Value.PurchaseUrl}/api/v1/c/catalog/";
@ -30,25 +30,26 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<Catalog> GetCatalogItems(int page, int take, int? brand, int? type)
{
var allcatalogItemsUri = API.Catalog.GetAllCatalogItems(_remoteServiceBaseUrl, page, take, brand, type);
var uri = API.Catalog.GetAllCatalogItems(_remoteServiceBaseUrl, page, take, brand, type);
var dataString = await _apiClient.GetStringAsync(allcatalogItemsUri);
var responseString = await _httpClient.GetStringAsync(uri);
var response = JsonConvert.DeserializeObject<Catalog>(dataString);
var catalog = JsonConvert.DeserializeObject<Catalog>(responseString);
return response;
return catalog;
}
public async Task<IEnumerable<SelectListItem>> GetBrands()
{
var getBrandsUri = API.Catalog.GetAllBrands(_remoteServiceBaseUrl);
var uri = API.Catalog.GetAllBrands(_remoteServiceBaseUrl);
var dataString = await _apiClient.GetStringAsync(getBrandsUri);
var responseString = await _httpClient.GetStringAsync(uri);
var items = new List<SelectListItem>();
items.Add(new SelectListItem() { Value = null, Text = "All", Selected = true });
var brands = JArray.Parse(dataString);
var brands = JArray.Parse(responseString);
foreach (var brand in brands.Children<JObject>())
{
@ -64,14 +65,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<IEnumerable<SelectListItem>> GetTypes()
{
var getTypesUri = API.Catalog.GetAllTypes(_remoteServiceBaseUrl);
var uri = API.Catalog.GetAllTypes(_remoteServiceBaseUrl);
var dataString = await _apiClient.GetStringAsync(getTypesUri);
var responseString = await _httpClient.GetStringAsync(uri);
var items = new List<SelectListItem>();
items.Add(new SelectListItem() { Value = null, Text = "All", Selected = true });
var brands = JArray.Parse(dataString);
var brands = JArray.Parse(responseString);
foreach (var brand in brands.Children<JObject>())
{
items.Add(new SelectListItem()
@ -80,6 +81,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
Text = brand.Value<string>("type")
});
}
return items;
}
}

View File

@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC;
using Microsoft.eShopOnContainers.WebMVC;
using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using Newtonsoft.Json;
using System.Net.Http;
using System.Threading.Tasks;
using WebMVC.Infrastructure;
using WebMVC.Models;
@ -14,36 +12,27 @@ namespace WebMVC.Services
{
public class LocationService : ILocationService
{
private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IHttpClient _apiClient;
private readonly IOptions<AppSettings> _settings;
private readonly HttpClient _httpClient;
private readonly ILogger<CampaignService> _logger;
private readonly string _remoteServiceBaseUrl;
private readonly IHttpContextAccessor _httpContextAccesor;
public LocationService(IOptionsSnapshot<AppSettings> settings, IHttpClient httpClient,
ILogger<CampaignService> logger, IHttpContextAccessor httpContextAccesor)
public LocationService(HttpClient httpClient, IOptions<AppSettings> settings, ILogger<CampaignService> logger)
{
_httpClient = httpClient;
_settings = settings;
_apiClient = httpClient;
_logger = logger;
_remoteServiceBaseUrl = $"{_settings.Value.MarketingUrl}/api/v1/l/locations/";
_httpContextAccesor = httpContextAccesor ?? throw new ArgumentNullException(nameof(httpContextAccesor));
}
public async Task CreateOrUpdateUserLocation(LocationDTO location)
{
var createOrUpdateUserLocationUri = API.Locations.CreateOrUpdateUserLocation(_remoteServiceBaseUrl);
var uri = API.Locations.CreateOrUpdateUserLocation(_remoteServiceBaseUrl);
var locationContent = new StringContent(JsonConvert.SerializeObject(location), System.Text.Encoding.UTF8, "application/json");
var authorizationToken = await GetUserTokenAsync();
var response = await _apiClient.PostAsync(createOrUpdateUserLocationUri, location, authorizationToken);
var response = await _httpClient.PostAsync(uri, locationContent);
response.EnsureSuccessStatusCode();
}
private async Task<string> GetUserTokenAsync()
{
var context = _httpContextAccesor.HttpContext;
return await context.GetTokenAsync("access_token");
}
}
}

View File

@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using WebMVC.Infrastructure;
using WebMVC.Models;
@ -14,69 +12,54 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public class OrderingService : IOrderingService
{
private IHttpClient _apiClient;
private HttpClient _httpClient;
private readonly string _remoteServiceBaseUrl;
private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IHttpContextAccessor _httpContextAccesor;
private readonly IOptions<AppSettings> _settings;
public OrderingService(IOptionsSnapshot<AppSettings> settings, IHttpContextAccessor httpContextAccesor, IHttpClient httpClient)
public OrderingService(HttpClient httpClient, IOptions<AppSettings> settings)
{
_remoteServiceBaseUrl = $"{settings.Value.PurchaseUrl}/api/v1/o/orders";
_httpClient = httpClient;
_settings = settings;
_httpContextAccesor = httpContextAccesor;
_apiClient = httpClient;
_remoteServiceBaseUrl = $"{settings.Value.PurchaseUrl}/api/v1/o/orders";
}
async public Task<Order> GetOrder(ApplicationUser user, string id)
{
var token = await GetUserTokenAsync();
var getOrderUri = API.Order.GetOrder(_remoteServiceBaseUrl, id);
var uri = API.Order.GetOrder(_remoteServiceBaseUrl, id);
var dataString = await _apiClient.GetStringAsync(getOrderUri, token);
var responseString = await _httpClient.GetStringAsync(uri);
var response = JsonConvert.DeserializeObject<Order>(dataString);
var response = JsonConvert.DeserializeObject<Order>(responseString);
return response;
}
async public Task<List<Order>> GetMyOrders(ApplicationUser user)
{
var token = await GetUserTokenAsync();
var allMyOrdersUri = API.Order.GetAllMyOrders(_remoteServiceBaseUrl);
var uri = API.Order.GetAllMyOrders(_remoteServiceBaseUrl);
var dataString = await _apiClient.GetStringAsync(allMyOrdersUri, token);
var response = JsonConvert.DeserializeObject<List<Order>>(dataString);
var responseString = await _httpClient.GetStringAsync(uri);
var response = JsonConvert.DeserializeObject<List<Order>>(responseString);
return response;
}
public Order MapUserInfoIntoOrder(ApplicationUser user, Order order)
{
order.City = user.City;
order.Street = user.Street;
order.State = user.State;
order.Country = user.Country;
order.ZipCode = user.ZipCode;
order.CardNumber = user.CardNumber;
order.CardHolderName = user.CardHolderName;
order.CardExpiration = new DateTime(int.Parse("20" + user.Expiration.Split('/')[1]), int.Parse(user.Expiration.Split('/')[0]), 1);
order.CardSecurityNumber = user.SecurityNumber;
return order;
}
async public Task CancelOrder(string orderId)
{
var token = await GetUserTokenAsync();
var order = new OrderDTO()
{
OrderNumber = orderId
};
var cancelOrderUri = API.Order.CancelOrder(_remoteServiceBaseUrl);
var response = await _apiClient.PutAsync(cancelOrderUri, order, token, Guid.NewGuid().ToString());
var uri = API.Order.CancelOrder(_remoteServiceBaseUrl);
var orderContent = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
var response = await _httpClient.PutAsync(uri, orderContent);
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{
@ -88,15 +71,15 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
async public Task ShipOrder(string orderId)
{
var token = await GetUserTokenAsync();
var order = new OrderDTO()
{
OrderNumber = orderId
};
var shipOrderUri = API.Order.ShipOrder(_remoteServiceBaseUrl);
var uri = API.Order.ShipOrder(_remoteServiceBaseUrl);
var orderContent = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PutAsync(shipOrderUri, order, token, Guid.NewGuid().ToString());
var response = await _httpClient.PutAsync(uri, orderContent);
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{
@ -120,6 +103,22 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
destination.CardSecurityNumber = original.CardSecurityNumber;
}
public Order MapUserInfoIntoOrder(ApplicationUser user, Order order)
{
order.City = user.City;
order.Street = user.Street;
order.State = user.State;
order.Country = user.Country;
order.ZipCode = user.ZipCode;
order.CardNumber = user.CardNumber;
order.CardHolderName = user.CardHolderName;
order.CardExpiration = new DateTime(int.Parse("20" + user.Expiration.Split('/')[1]), int.Parse(user.Expiration.Split('/')[0]), 1);
order.CardSecurityNumber = user.SecurityNumber;
return order;
}
public BasketDTO MapOrderToBasket(Order order)
{
order.CardExpirationApiFormat();
@ -140,18 +139,5 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
RequestId = order.RequestId
};
}
void SetFakeIdToProducts(Order order)
{
var id = 1;
order.OrderItems.ForEach(x => { x.ProductId = id; id++; });
}
async Task<string> GetUserTokenAsync()
{
var context = _httpContextAccesor.HttpContext;
return await context.GetTokenAsync("access_token");
}
}
}

View File

@ -6,8 +6,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.Infrastructure;
using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Configuration;
@ -15,15 +13,10 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging;
using Polly;
using Polly.CircuitBreaker;
using Polly.Extensions.Http;
using Polly.Registry;
using Polly.Retry;
using Polly.Timeout;
using StackExchange.Redis;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using WebMVC.Infrastructure;
using WebMVC.Infrastructure.Middlewares;
using WebMVC.Services;
@ -151,6 +144,9 @@ namespace Microsoft.eShopOnContainers.WebMVC
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions();
services.Configure<AppSettings>(configuration);
services.AddMvc();
services.AddSession();
@ -163,124 +159,58 @@ namespace Microsoft.eShopOnContainers.WebMVC
})
.PersistKeysToRedis(ConnectionMultiplexer.Connect(configuration["DPConnectionString"]), "DataProtection-Keys");
}
services.Configure<AppSettings>(configuration);
return services;
}
// Adds all Http client services (like Service-Agents) using resilient Http requests based on HttpClient factory and Polly's policies
public static IServiceCollection AddHttpClientServices(this IServiceCollection services, IConfiguration configuration)
{
// (CDLTLL) TEMPORAL COMMENT: Do we need this line of code if using HttpClient factory?
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// (CDLTLL) I don't think we need this HttpClientDefaultPolicies if using fluent configuration ,as below...
// Create a singleton object with the by default policies
services.AddSingleton<HttpClientDefaultPolicies>();
var defaultPolicies = services.BuildServiceProvider().GetService<HttpClientDefaultPolicies>();
// Create a Polly-Policy-Registry with the by default policies for resilient Http requests
var pollyPolicyRegistry = services.AddPolicyRegistry();
//Using fluent client configuration of Polly policies
var retriesWithExponentialBackoff = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(7,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
);
//(CDLTLL) Instead of hardcoded values, use: configuration["HttpClientMaxNumberRetries"], configuration["SecondsBaseForExponentialBackoff"]
var retriesWithExponentialBackoff = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(7,retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
var circuitBreaker = HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(6,
TimeSpan.FromSeconds(30)
);
//(CDLTLL) Instead of hardcoded values, use: configuration["HttpClientExceptionsAllowedBeforeBreaking"], configuration["DurationOfBreakInMinutes"]
.HandleTransientHttpError()
.CircuitBreakerAsync(6, TimeSpan.FromSeconds(30));
pollyPolicyRegistry.Add("DefaultRetriesWithExponentialBackoff", retriesWithExponentialBackoff);
pollyPolicyRegistry.Add("DefaultCircuitBreaker", circuitBreaker);
// (CDLTLL) Using "OLD" policies. I don't like it much...
//pollyPolicyRegistry.Add("DefaultRetriesWithExponentialBackoff", defaultPolicies.GetWaitAndRetryPolicy()); // (CDLTLL) TEMPORAL COMMENT: Arguments here would be a good place to propagate the configuration values --> GetWaitAndRetryPolicy(configuration["HttpClientMaxNumberRetries"], configuration["SecondsBaseForExponentialBackoff"])
//pollyPolicyRegistry.Add("DefaultCircuitBreaker", defaultPolicies.GetCircuitBreakerPolicy()); // (CDLTLL) TEMPORAL COMMENT: Arguments here would be a good place to propagate the configuration values --> GetCircuitBreakerPolicy(configuration["HttpClientExceptionsAllowedBeforeBreaking"], configuration["DurationOfBreakInMinutes"])
// (CDLTLL) Handlers need to be transient. //(CDLTLL) TEMPORAL COMMENT: This registration was missing, hence the error "InvalidOperationException: No service for type 'WebMVC.Infrastructure.HttpClientAuthorizationDelegatingHandler' has been registered"
//register delegating handlers
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
services.AddTransient<HttpClientRequestIdDelegatingHandler>();
//Add all Typed-Clients (Service-Agents) through the HttpClient factory to implement Resilient Http requests
//
//Add BasketService typed client (Service Agent)
//add http client servicse
services.AddHttpClient<IBasketService, BasketService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>() //Additional Authentication-Delegating-Handler to add the OAuth-Bearer-token to the Http headers
.AddPolicyHandlerFromRegistry("DefaultRetriesWithExponentialBackoff")
.AddPolicyHandlerFromRegistry("DefaultCircuitBreaker");
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
//Add CatalogService typed client (Service Agent)
//services.AddHttpClient<ICatalogService, CatalogService>() ...
services.AddHttpClient<ICatalogService, CatalogService>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
//Add OrderingService typed client (Service Agent)
//services.AddHttpClient<IOrderingService, OrderingService>() ...
services.AddHttpClient<IOrderingService, OrderingService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddHttpMessageHandler<HttpClientRequestIdDelegatingHandler>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
//Add CampaignService typed client (Service Agent)
//services.AddHttpClient<ICampaignService, CampaignService>() ...
services.AddHttpClient<ICampaignService, CampaignService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
//Add LocationService typed client (Service Agent)
//services.AddHttpClient<ILocationService, LocationService>() ...
services.AddHttpClient<ILocationService, LocationService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
//Add IdentityParser typed client (Service Agent)
//services.AddHttpClient<IIdentityParser, IdentityParser>() ...
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// (CDLTLL) TEMPORAL COMMENT: The following Typed-Clients (Service-Agents) have to be coded as BasketService by using AddHttpClient<>(), right?
// This code will be deleted
services.AddTransient<ICatalogService, CatalogService>();
services.AddTransient<IOrderingService, OrderingService>();
services.AddTransient<ICampaignService, CampaignService>();
services.AddTransient<ILocationService, LocationService>();
//add custom application services
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TEMPORAL COMMENT: This code will be deleted when using HttpClientFactory, right?
if (configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
{
services.AddSingleton<IResilientHttpClientFactory, ResilientHttpClientFactory>(sp =>
{
var logger = sp.GetRequiredService<ILogger<ResilientHttpClient>>();
var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
var retryCount = 6;
if (!string.IsNullOrEmpty(configuration["HttpClientRetryCount"]))
{
retryCount = int.Parse(configuration["HttpClientRetryCount"]);
}
var exceptionsAllowedBeforeBreaking = 5;
if (!string.IsNullOrEmpty(configuration["HttpClientExceptionsAllowedBeforeBreaking"]))
{
exceptionsAllowedBeforeBreaking = int.Parse(configuration["HttpClientExceptionsAllowedBeforeBreaking"]);
}
return new ResilientHttpClientFactory(logger, httpContextAccessor, exceptionsAllowedBeforeBreaking, retryCount);
});
services.AddSingleton<IHttpClient, ResilientHttpClient>(sp => sp.GetService<IResilientHttpClientFactory>().CreateResilientHttpClient());
}
else
{
services.AddSingleton<IHttpClient, StandardHttpClient>();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return services;
}
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)