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); var basket = await _repository.GetBasketAsync(id);
if (basket == null) if (basket == null)
{ {
return NotFound(); return Ok(new CustomerBasket(id) { });
} }
return Ok(basket); return Ok(basket);

View File

@ -7,9 +7,8 @@ namespace Microsoft.eShopOnContainers.WebMVC
{ {
public class AppSettings public class AppSettings
{ {
public Connectionstrings ConnectionStrings { get; set; } //public Connectionstrings ConnectionStrings { get; set; }
public string MarketingUrl { get; set; } public string MarketingUrl { get; set; }
public string PurchaseUrl { get; set; } public string PurchaseUrl { get; set; }
public string SignalrHubUrl { get; set; } public string SignalrHubUrl { get; set; }
public bool ActivateCampaignDetailFunction { 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 namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
{ {
public interface IResilientHttpClientFactory //public interface IResilientHttpClientFactory
{ //{
ResilientHttpClient CreateResilientHttpClient(); // ResilientHttpClient CreateResilientHttpClient();
} //}
} }

View File

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

View File

@ -12,14 +12,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
public class BasketService : IBasketService public class BasketService : IBasketService
{ {
private readonly IOptionsSnapshot<AppSettings> _settings; private readonly IOptions<AppSettings> _settings;
private readonly HttpClient _apiClient; private readonly HttpClient _apiClient;
private readonly string _basketByPassUrl; private readonly string _basketByPassUrl;
private readonly string _purchaseUrl; private readonly string _purchaseUrl;
private readonly string _bffUrl; private readonly string _bffUrl;
public BasketService(HttpClient httpClient,IOptionsSnapshot<AppSettings> settings) public BasketService(HttpClient httpClient, IOptions<AppSettings> settings)
{ {
_apiClient = httpClient; _apiClient = httpClient;
_settings = settings; _settings = settings;
@ -30,21 +30,22 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<Basket> GetBasket(ApplicationUser user) 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) ? return string.IsNullOrEmpty(responseString) ?
new Basket() { BuyerId = user.Id } : new Basket() { BuyerId = user.Id } :
JsonConvert.DeserializeObject<Basket>(dataString); JsonConvert.DeserializeObject<Basket>(responseString);
} }
public async Task<Basket> UpdateBasket(Basket basket) public async Task<Basket> UpdateBasket(Basket basket)
{ {
var updateBasketUri = API.Basket.UpdateBasket(_basketByPassUrl); var uri = API.Basket.UpdateBasket(_basketByPassUrl);
var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
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(); response.EnsureSuccessStatusCode();
@ -53,18 +54,18 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task Checkout(BasketDTO basket) public async Task Checkout(BasketDTO basket)
{ {
var updateBasketUri = API.Basket.CheckoutBasket(_basketByPassUrl); var uri = API.Basket.CheckoutBasket(_basketByPassUrl);
var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json"); 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(); response.EnsureSuccessStatusCode();
} }
public async Task<Basket> SetQuantities(ApplicationUser user, Dictionary<string, int> quantities) 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 var basketUpdate = new
{ {
BasketId = user.Id, BasketId = user.Id,
@ -75,9 +76,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
}).ToArray() }).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(); response.EnsureSuccessStatusCode();
@ -88,17 +89,18 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<Order> GetOrderDraft(string basketId) public async Task<Order> GetOrderDraft(string basketId)
{ {
var draftOrderUri = API.Purchase.GetOrderDraft(_purchaseUrl, basketId); var uri = API.Purchase.GetOrderDraft(_purchaseUrl, basketId);
var response = await _apiClient.GetStringAsync(draftOrderUri);
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) public async Task AddItemToBasket(ApplicationUser user, int productId)
{ {
var updateBasketUri = API.Purchase.AddItemToBasket(_purchaseUrl); var uri = API.Purchase.AddItemToBasket(_purchaseUrl);
var newItem = new var newItem = new
{ {
@ -107,9 +109,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
Quantity = 1 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 namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
using global::WebMVC.Infrastructure; using global::WebMVC.Infrastructure;
using AspNetCore.Authentication;
using AspNetCore.Http;
using BuildingBlocks.Resilience.Http;
using ViewModels;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using ViewModels;
public class CampaignService : ICampaignService public class CampaignService : ICampaignService
{ {
private readonly IOptionsSnapshot<AppSettings> _settings; private readonly IOptions<AppSettings> _settings;
private readonly IHttpClient _apiClient; private readonly HttpClient _httpClient;
private readonly ILogger<CampaignService> _logger; private readonly ILogger<CampaignService> _logger;
private readonly string _remoteServiceBaseUrl; private readonly string _remoteServiceBaseUrl;
private readonly IHttpContextAccessor _httpContextAccesor;
public CampaignService(IOptionsSnapshot<AppSettings> settings, IHttpClient httpClient, public CampaignService(IOptions<AppSettings> settings, HttpClient httpClient, ILogger<CampaignService> logger)
ILogger<CampaignService> logger, IHttpContextAccessor httpContextAccesor)
{ {
_settings = settings; _settings = settings;
_apiClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
_remoteServiceBaseUrl = $"{_settings.Value.MarketingUrl}/api/v1/m/campaigns/"; _remoteServiceBaseUrl = $"{_settings.Value.MarketingUrl}/api/v1/m/campaigns/";
_httpContextAccesor = httpContextAccesor ?? throw new ArgumentNullException(nameof(httpContextAccesor));
} }
public async Task<Campaign> GetCampaigns(int pageSize, int pageIndex) public async Task<Campaign> GetCampaigns(int pageSize, int pageIndex)
{ {
var allCampaignItemsUri = API.Marketing.GetAllCampaigns(_remoteServiceBaseUrl, var uri = API.Marketing.GetAllCampaigns(_remoteServiceBaseUrl, pageSize, pageIndex);
pageSize, pageIndex);
var authorizationToken = await GetUserTokenAsync(); var responseString = await _httpClient.GetStringAsync(uri);
var dataString = await _apiClient.GetStringAsync(allCampaignItemsUri, authorizationToken);
var response = JsonConvert.DeserializeObject<Campaign>(dataString); var response = JsonConvert.DeserializeObject<Campaign>(responseString);
return response; return response;
} }
public async Task<CampaignItem> GetCampaignById(int id) 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 responseString = await _httpClient.GetStringAsync(uri);
var dataString = await _apiClient.GetStringAsync(campaignByIdItemUri, authorizationToken);
var response = JsonConvert.DeserializeObject<CampaignItem>(dataString); var response = JsonConvert.DeserializeObject<CampaignItem>(responseString);
return response; 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.AspNetCore.Mvc.Rendering;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using WebMVC.Infrastructure; using WebMVC.Infrastructure;
@ -13,16 +13,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
public class CatalogService : ICatalogService public class CatalogService : ICatalogService
{ {
private readonly IOptionsSnapshot<AppSettings> _settings; private readonly IOptions<AppSettings> _settings;
private readonly IHttpClient _apiClient; private readonly HttpClient _httpClient;
private readonly ILogger<CatalogService> _logger; private readonly ILogger<CatalogService> _logger;
private readonly string _remoteServiceBaseUrl; 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; _settings = settings;
_apiClient = httpClient;
_logger = logger; _logger = logger;
_remoteServiceBaseUrl = $"{_settings.Value.PurchaseUrl}/api/v1/c/catalog/"; _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) 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() 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>(); var items = new List<SelectListItem>();
items.Add(new SelectListItem() { Value = null, Text = "All", Selected = true }); 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>()) foreach (var brand in brands.Children<JObject>())
{ {
@ -64,14 +65,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<IEnumerable<SelectListItem>> GetTypes() 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>(); var items = new List<SelectListItem>();
items.Add(new SelectListItem() { Value = null, Text = "All", Selected = true }); 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>()) foreach (var brand in brands.Children<JObject>())
{ {
items.Add(new SelectListItem() items.Add(new SelectListItem()
@ -80,6 +81,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
Text = brand.Value<string>("type") Text = brand.Value<string>("type")
}); });
} }
return items; return items;
} }
} }

View File

@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Authentication; using Microsoft.eShopOnContainers.WebMVC;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC;
using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System; using Newtonsoft.Json;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using WebMVC.Infrastructure; using WebMVC.Infrastructure;
using WebMVC.Models; using WebMVC.Models;
@ -14,36 +12,27 @@ namespace WebMVC.Services
{ {
public class LocationService : ILocationService public class LocationService : ILocationService
{ {
private readonly IOptionsSnapshot<AppSettings> _settings; private readonly IOptions<AppSettings> _settings;
private readonly IHttpClient _apiClient; private readonly HttpClient _httpClient;
private readonly ILogger<CampaignService> _logger; private readonly ILogger<CampaignService> _logger;
private readonly string _remoteServiceBaseUrl; private readonly string _remoteServiceBaseUrl;
private readonly IHttpContextAccessor _httpContextAccesor;
public LocationService(IOptionsSnapshot<AppSettings> settings, IHttpClient httpClient, public LocationService(HttpClient httpClient, IOptions<AppSettings> settings, ILogger<CampaignService> logger)
ILogger<CampaignService> logger, IHttpContextAccessor httpContextAccesor)
{ {
_httpClient = httpClient;
_settings = settings; _settings = settings;
_apiClient = httpClient;
_logger = logger; _logger = logger;
_remoteServiceBaseUrl = $"{_settings.Value.MarketingUrl}/api/v1/l/locations/"; _remoteServiceBaseUrl = $"{_settings.Value.MarketingUrl}/api/v1/l/locations/";
_httpContextAccesor = httpContextAccesor ?? throw new ArgumentNullException(nameof(httpContextAccesor));
} }
public async Task CreateOrUpdateUserLocation(LocationDTO location) 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 _httpClient.PostAsync(uri, locationContent);
var response = await _apiClient.PostAsync(createOrUpdateUserLocationUri, location, authorizationToken);
response.EnsureSuccessStatusCode(); 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.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using WebMVC.Infrastructure; using WebMVC.Infrastructure;
using WebMVC.Models; using WebMVC.Models;
@ -14,69 +12,54 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
public class OrderingService : IOrderingService public class OrderingService : IOrderingService
{ {
private IHttpClient _apiClient; private HttpClient _httpClient;
private readonly string _remoteServiceBaseUrl; private readonly string _remoteServiceBaseUrl;
private readonly IOptionsSnapshot<AppSettings> _settings; private readonly IOptions<AppSettings> _settings;
private readonly IHttpContextAccessor _httpContextAccesor;
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; _settings = settings;
_httpContextAccesor = httpContextAccesor;
_apiClient = httpClient; _remoteServiceBaseUrl = $"{settings.Value.PurchaseUrl}/api/v1/o/orders";
} }
async public Task<Order> GetOrder(ApplicationUser user, string id) async public Task<Order> GetOrder(ApplicationUser user, string id)
{ {
var token = await GetUserTokenAsync(); var uri = API.Order.GetOrder(_remoteServiceBaseUrl, id);
var getOrderUri = 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; return response;
} }
async public Task<List<Order>> GetMyOrders(ApplicationUser user) async public Task<List<Order>> GetMyOrders(ApplicationUser user)
{ {
var token = await GetUserTokenAsync(); var uri = API.Order.GetAllMyOrders(_remoteServiceBaseUrl);
var allMyOrdersUri = API.Order.GetAllMyOrders(_remoteServiceBaseUrl);
var dataString = await _apiClient.GetStringAsync(allMyOrdersUri, token); var responseString = await _httpClient.GetStringAsync(uri);
var response = JsonConvert.DeserializeObject<List<Order>>(dataString);
var response = JsonConvert.DeserializeObject<List<Order>>(responseString);
return response; 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) async public Task CancelOrder(string orderId)
{ {
var token = await GetUserTokenAsync();
var order = new OrderDTO() var order = new OrderDTO()
{ {
OrderNumber = orderId OrderNumber = orderId
}; };
var cancelOrderUri = API.Order.CancelOrder(_remoteServiceBaseUrl); var uri = API.Order.CancelOrder(_remoteServiceBaseUrl);
var orderContent = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PutAsync(cancelOrderUri, order, token, Guid.NewGuid().ToString()); var response = await _httpClient.PutAsync(uri, orderContent);
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError) if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{ {
@ -88,15 +71,15 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
async public Task ShipOrder(string orderId) async public Task ShipOrder(string orderId)
{ {
var token = await GetUserTokenAsync();
var order = new OrderDTO() var order = new OrderDTO()
{ {
OrderNumber = orderId 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) if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
{ {
@ -120,6 +103,22 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
destination.CardSecurityNumber = original.CardSecurityNumber; 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) public BasketDTO MapOrderToBasket(Order order)
{ {
order.CardExpirationApiFormat(); order.CardExpirationApiFormat();
@ -140,18 +139,5 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
RequestId = order.RequestId 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.DataProtection;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.Infrastructure;
using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -15,15 +13,10 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Polly; using Polly;
using Polly.CircuitBreaker;
using Polly.Extensions.Http; using Polly.Extensions.Http;
using Polly.Registry;
using Polly.Retry;
using Polly.Timeout;
using StackExchange.Redis; using StackExchange.Redis;
using System; using System;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using WebMVC.Infrastructure; using WebMVC.Infrastructure;
using WebMVC.Infrastructure.Middlewares; using WebMVC.Infrastructure.Middlewares;
using WebMVC.Services; using WebMVC.Services;
@ -151,6 +144,9 @@ namespace Microsoft.eShopOnContainers.WebMVC
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration)
{ {
services.AddOptions();
services.Configure<AppSettings>(configuration);
services.AddMvc(); services.AddMvc();
services.AddSession(); services.AddSession();
@ -163,123 +159,57 @@ namespace Microsoft.eShopOnContainers.WebMVC
}) })
.PersistKeysToRedis(ConnectionMultiplexer.Connect(configuration["DPConnectionString"]), "DataProtection-Keys"); .PersistKeysToRedis(ConnectionMultiplexer.Connect(configuration["DPConnectionString"]), "DataProtection-Keys");
} }
services.Configure<AppSettings>(configuration);
return services; return services;
} }
// Adds all Http client services (like Service-Agents) using resilient Http requests based on HttpClient factory and Polly's policies // 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) 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>(); 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 //Using fluent client configuration of Polly policies
//(CDLTLL) Instead of hardcoded values, use: configuration["HttpClientMaxNumberRetries"], configuration["SecondsBaseForExponentialBackoff"]
var retriesWithExponentialBackoff = HttpPolicyExtensions var retriesWithExponentialBackoff = HttpPolicyExtensions
.HandleTransientHttpError() .HandleTransientHttpError()
.WaitAndRetryAsync(7, .WaitAndRetryAsync(7,retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
);
//(CDLTLL) Instead of hardcoded values, use: configuration["HttpClientMaxNumberRetries"], configuration["SecondsBaseForExponentialBackoff"]
var circuitBreaker = HttpPolicyExtensions var circuitBreaker = HttpPolicyExtensions
.HandleTransientHttpError() .HandleTransientHttpError()
.CircuitBreakerAsync(6, .CircuitBreakerAsync(6, TimeSpan.FromSeconds(30));
TimeSpan.FromSeconds(30)
);
//(CDLTLL) Instead of hardcoded values, use: configuration["HttpClientExceptionsAllowedBeforeBreaking"], configuration["DurationOfBreakInMinutes"]
//register delegating handlers
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"
services.AddTransient<HttpClientAuthorizationDelegatingHandler>(); services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
services.AddTransient<HttpClientRequestIdDelegatingHandler>();
//Add all Typed-Clients (Service-Agents) through the HttpClient factory to implement Resilient Http requests //add http client servicse
//
//Add BasketService typed client (Service Agent)
services.AddHttpClient<IBasketService, BasketService>() services.AddHttpClient<IBasketService, BasketService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>() //Additional Authentication-Delegating-Handler to add the OAuth-Bearer-token to the Http headers .AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandlerFromRegistry("DefaultRetriesWithExponentialBackoff") .AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandlerFromRegistry("DefaultCircuitBreaker"); .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) //add custom application services
//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>();
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>(); 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; return services;
} }