Refactor aggregators to use AddHttpClient like on WebMVC

This commit is contained in:
Unai Zorrilla Castro 2018-05-18 14:02:18 +02:00
parent 554b4e4a0c
commit 96816e7bf7
12 changed files with 414 additions and 261 deletions

View File

@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure
{
public class HttpClientAuthorizationDelegatingHandler
: DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccesor;
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccesor)
{
_httpContextAccesor = httpContextAccesor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var authorizationHeader = _httpContextAccesor.HttpContext
.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authorizationHeader))
{
request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
}
var token = await GetToken();
if (token != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
async Task<string> GetToken()
{
const string ACCESS_TOKEN = "access_token";
return await _httpContextAccesor.HttpContext
.GetTokenAsync(ACCESS_TOKEN);
}
}
}

View File

@ -14,6 +14,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" /> <PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.1.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="2.1.0-rc1-final" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,53 +1,41 @@
using System; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using System.Collections.Generic; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; using System.Net.Http;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{ {
public class BasketService : IBasketService public class BasketService : IBasketService
{ {
private readonly IHttpClient _apiClient; private readonly HttpClient _httpClient;
private readonly ILogger<BasketService> _logger; private readonly ILogger<BasketService> _logger;
private readonly UrlsConfig _urls; private readonly UrlsConfig _urls;
private readonly IHttpContextAccessor _httpContextAccessor;
public BasketService(IHttpClient httpClient, IHttpContextAccessor httpContextAccessor, ILogger<BasketService> logger, IOptionsSnapshot<UrlsConfig> config) public BasketService(HttpClient httpClient, ILogger<BasketService> logger, IOptions<UrlsConfig> config)
{ {
_apiClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
_urls = config.Value; _urls = config.Value;
_httpContextAccessor = httpContextAccessor;
} }
public async Task<BasketData> GetById(string id) public async Task<BasketData> GetById(string id)
{ {
var token = await GetUserTokenAsync(); var data = await _httpClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id));
var data = await _apiClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id), token);
var basket = !string.IsNullOrEmpty(data) ? JsonConvert.DeserializeObject<BasketData>(data) : null; var basket = !string.IsNullOrEmpty(data) ? JsonConvert.DeserializeObject<BasketData>(data) : null;
return basket; return basket;
} }
public async Task Update(BasketData currentBasket) public async Task Update(BasketData currentBasket)
{ {
var token = await GetUserTokenAsync(); var basketContent = new StringContent(JsonConvert.SerializeObject(currentBasket), System.Text.Encoding.UTF8, "application/json");
var data = await _apiClient.PostAsync<BasketData>(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), currentBasket, token);
int i = 0;
}
async Task<string> GetUserTokenAsync() var data = await _httpClient.PostAsync(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), basketContent);
{
var context = _httpContextAccessor.HttpContext;
return await context.GetTokenAsync("access_token");
} }
} }
} }

View File

@ -1,43 +1,41 @@
using System; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using System.Collections.Generic; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; using System.Collections.Generic;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; using System.Net.Http;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{ {
public class CatalogService : ICatalogService public class CatalogService : ICatalogService
{ {
private readonly HttpClient _httpClient;
private readonly IHttpClient _apiClient;
private readonly ILogger<CatalogService> _logger; private readonly ILogger<CatalogService> _logger;
private readonly UrlsConfig _urls; private readonly UrlsConfig _urls;
public CatalogService(IHttpClient httpClient, ILogger<CatalogService> logger, IOptionsSnapshot<UrlsConfig> config) public CatalogService(HttpClient httpClient, ILogger<CatalogService> logger, IOptions<UrlsConfig> config)
{ {
_apiClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
_urls = config.Value; _urls = config.Value;
} }
public async Task<CatalogItem> GetCatalogItem(int id) public async Task<CatalogItem> GetCatalogItem(int id)
{ {
var data = await _apiClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id)); var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id));
var item = JsonConvert.DeserializeObject<CatalogItem>(data); var catalogItem = JsonConvert.DeserializeObject<CatalogItem>(stringContent);
return item;
return catalogItem;
} }
public async Task<IEnumerable<CatalogItem>> GetCatalogItems(IEnumerable<int> ids) public async Task<IEnumerable<CatalogItem>> GetCatalogItems(IEnumerable<int> ids)
{ {
var data = await _apiClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids)); var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids));
var item = JsonConvert.DeserializeObject<CatalogItem[]>(data); var catalogItems = JsonConvert.DeserializeObject<CatalogItem[]>(stringContent);
return item;
return catalogItems;
} }
} }
} }

View File

@ -1,24 +1,20 @@
using System; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using System.Collections.Generic; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; using System.Net.Http;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{ {
public class OrderApiClient : IOrderApiClient public class OrderApiClient : IOrderApiClient
{ {
private readonly HttpClient _apiClient;
private readonly IHttpClient _apiClient;
private readonly ILogger<OrderApiClient> _logger; private readonly ILogger<OrderApiClient> _logger;
private readonly UrlsConfig _urls; private readonly UrlsConfig _urls;
public OrderApiClient(IHttpClient httpClient, ILogger<OrderApiClient> logger, IOptionsSnapshot<UrlsConfig> config) public OrderApiClient(HttpClient httpClient, ILogger<OrderApiClient> logger, IOptions<UrlsConfig> config)
{ {
_apiClient = httpClient; _apiClient = httpClient;
_logger = logger; _logger = logger;
@ -27,11 +23,15 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
public async Task<OrderData> GetOrderDraftFromBasket(BasketData basket) public async Task<OrderData> GetOrderDraftFromBasket(BasketData basket)
{ {
var url = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft(); var uri = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft();
var response = await _apiClient.PostAsync<BasketData>(url, basket); var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(uri, content);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<OrderData>(jsonResponse); var ordersDraftResponse = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<OrderData>(ordersDraftResponse);
} }
} }
} }

View File

@ -16,6 +16,9 @@ using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.Swagger;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure;
using Polly.Extensions.Http;
using Polly;
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator
{ {
@ -31,84 +34,16 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddCustomMvc(Configuration)
services.AddSingleton<IHttpClient, StandardHttpClient>(); .AddCustomAuthentication(Configuration)
services.AddTransient<ICatalogService, CatalogService>(); .AddHttpServices();
services.AddTransient<IBasketService, BasketService>();
services.AddTransient<IOrderApiClient, OrderApiClient>();
services.AddOptions();
services.Configure<UrlsConfig>(Configuration.GetSection("urls"));
services.AddMvc();
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "Shopping Aggregator for Mobile Clients",
Version = "v1",
Description = "Shopping Aggregator for Mobile Clients",
TermsOfService = "Terms Of Service"
});
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize",
TokenUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
Scopes = new Dictionary<string, string>()
{
{ "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" }
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var identityUrl = Configuration.GetValue<string>("urls:identity");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "mobileshoppingagg";
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = async ctx =>
{
int i = 0;
},
OnTokenValidated = async ctx =>
{
int i = 0;
}
};
});
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ {
var pathBase = Configuration["PATH_BASE"]; var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase)) if (!string.IsNullOrEmpty(pathBase))
{ {
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
@ -131,8 +66,115 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1");
c.ConfigureOAuth2("Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregatorwaggerui", "", "", "Purchase BFF Swagger UI"); c.ConfigureOAuth2("Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregatorwaggerui", "", "", "Purchase BFF Swagger UI");
}); });
}
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions();
services.Configure<UrlsConfig>(configuration.GetSection("urls"));
services.AddMvc();
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "Shopping Aggregator for Mobile Clients",
Version = "v1",
Description = "Shopping Aggregator for Mobile Clients",
TermsOfService = "Terms Of Service"
});
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize",
TokenUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
Scopes = new Dictionary<string, string>()
{
{ "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" }
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
return services;
}
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var identityUrl = configuration.GetValue<string>("urls:identity");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "mobileshoppingagg";
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = async ctx =>
{
int i = 0;
},
OnTokenValidated = async ctx =>
{
int i = 0;
}
};
});
return services;
}
public static IServiceCollection AddHttpServices(this IServiceCollection services)
{
//register delegating handlers
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//register http services
var retriesWithExponentialBackoff = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(7, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
var circuitBreaker = HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(6, TimeSpan.FromSeconds(30));
services.AddHttpClient<IBasketService, BasketService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
services.AddHttpClient<ICatalogService, CatalogService>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
return services;
} }
} }
} }

View File

@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure
{
public class HttpClientAuthorizationDelegatingHandler
: DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccesor;
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccesor)
{
_httpContextAccesor = httpContextAccesor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var authorizationHeader = _httpContextAccesor.HttpContext
.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authorizationHeader))
{
request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
}
var token = await GetToken();
if (token != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
async Task<string> GetToken()
{
const string ACCESS_TOKEN = "access_token";
return await _httpContextAccesor.HttpContext
.GetTokenAsync(ACCESS_TOKEN);
}
}
}

View File

@ -1,53 +1,39 @@
using System; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using System.Collections.Generic; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; using System.Net.Http;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{ {
public class BasketService : IBasketService public class BasketService : IBasketService
{ {
private readonly IHttpClient _apiClient; private readonly HttpClient _apiClient;
private readonly ILogger<BasketService> _logger; private readonly ILogger<BasketService> _logger;
private readonly UrlsConfig _urls; private readonly UrlsConfig _urls;
private readonly IHttpContextAccessor _httpContextAccessor;
public BasketService(IHttpClient httpClient, IHttpContextAccessor httpContextAccessor, ILogger<BasketService> logger, IOptionsSnapshot<UrlsConfig> config) public BasketService(HttpClient httpClient,ILogger<BasketService> logger, IOptions<UrlsConfig> config)
{ {
_apiClient = httpClient; _apiClient = httpClient;
_logger = logger; _logger = logger;
_urls = config.Value; _urls = config.Value;
_httpContextAccessor = httpContextAccessor;
} }
public async Task<BasketData> GetById(string id) public async Task<BasketData> GetById(string id)
{ {
var token = await GetUserTokenAsync(); var data = await _apiClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id));
var data = await _apiClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id), token);
var basket = !string.IsNullOrEmpty(data) ? JsonConvert.DeserializeObject<BasketData>(data) : null; var basket = !string.IsNullOrEmpty(data) ? JsonConvert.DeserializeObject<BasketData>(data) : null;
return basket; return basket;
} }
public async Task Update(BasketData currentBasket) public async Task Update(BasketData currentBasket)
{ {
var token = await GetUserTokenAsync(); var basketContent = new StringContent(JsonConvert.SerializeObject(currentBasket), System.Text.Encoding.UTF8, "application/json");
var data = await _apiClient.PostAsync<BasketData>(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), currentBasket, token);
int i = 0;
}
async Task<string> GetUserTokenAsync() var data = await _apiClient.PostAsync(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), basketContent);
{
var context = _httpContextAccessor.HttpContext;
return await context.GetTokenAsync("access_token");
} }
} }
} }

View File

@ -1,43 +1,42 @@
using System; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using System.Collections.Generic; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; using System.Collections.Generic;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; using System.Net.Http;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{ {
public class CatalogService : ICatalogService public class CatalogService : ICatalogService
{ {
private readonly IHttpClient _apiClient; private readonly HttpClient _httpClient;
private readonly ILogger<CatalogService> _logger; private readonly ILogger<CatalogService> _logger;
private readonly UrlsConfig _urls; private readonly UrlsConfig _urls;
public CatalogService(IHttpClient httpClient, ILogger<CatalogService> logger, IOptionsSnapshot<UrlsConfig> config) public CatalogService(HttpClient httpClient, ILogger<CatalogService> logger, IOptions<UrlsConfig> config)
{ {
_apiClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
_urls = config.Value; _urls = config.Value;
} }
public async Task<CatalogItem> GetCatalogItem(int id) public async Task<CatalogItem> GetCatalogItem(int id)
{ {
var data = await _apiClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id)); var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id));
var item = JsonConvert.DeserializeObject<CatalogItem>(data); var catalogItem = JsonConvert.DeserializeObject<CatalogItem>(stringContent);
return item;
return catalogItem;
} }
public async Task<IEnumerable<CatalogItem>> GetCatalogItems(IEnumerable<int> ids) public async Task<IEnumerable<CatalogItem>> GetCatalogItems(IEnumerable<int> ids)
{ {
var data = await _apiClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids)); var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids));
var item = JsonConvert.DeserializeObject<CatalogItem[]>(data); var catalogItems = JsonConvert.DeserializeObject<CatalogItem[]>(stringContent);
return item;
return catalogItems;
} }
} }
} }

View File

@ -1,24 +1,21 @@
using System; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using System.Collections.Generic; using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; using System.Net.Http;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{ {
public class OrderApiClient : IOrderApiClient public class OrderApiClient : IOrderApiClient
{ {
private readonly IHttpClient _apiClient; private readonly HttpClient _apiClient;
private readonly ILogger<OrderApiClient> _logger; private readonly ILogger<OrderApiClient> _logger;
private readonly UrlsConfig _urls; private readonly UrlsConfig _urls;
public OrderApiClient(IHttpClient httpClient, ILogger<OrderApiClient> logger, IOptionsSnapshot<UrlsConfig> config) public OrderApiClient(HttpClient httpClient, ILogger<OrderApiClient> logger, IOptions<UrlsConfig> config)
{ {
_apiClient = httpClient; _apiClient = httpClient;
_logger = logger; _logger = logger;
@ -28,10 +25,14 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
public async Task<OrderData> GetOrderDraftFromBasket(BasketData basket) public async Task<OrderData> GetOrderDraftFromBasket(BasketData basket)
{ {
var url = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft(); var url = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft();
var response = await _apiClient.PostAsync<BasketData>(url, basket); var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(url, content);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<OrderData>(jsonResponse); var ordersDraftResponse = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<OrderData>(ordersDraftResponse);
} }
} }
} }

View File

@ -1,21 +1,20 @@
using System; using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
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.Web.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Polly;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; using Polly.Extensions.Http;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.Swagger;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator
{ {
@ -31,83 +30,14 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddCustomMvc(Configuration)
services.AddSingleton<IHttpClient, StandardHttpClient>(); .AddCustomAuthentication(Configuration)
services.AddTransient<ICatalogService, CatalogService>(); .AddApplicationServices();
services.AddTransient<IBasketService, BasketService>();
services.AddTransient<IOrderApiClient, OrderApiClient>();
services.AddOptions();
services.Configure<UrlsConfig>(Configuration.GetSection("urls"));
services.AddMvc();
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "Shopping Aggregator for Web Clients",
Version = "v1",
Description = "Shopping Aggregator for Web Clients",
TermsOfService = "Terms Of Service"
});
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize",
TokenUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
Scopes = new Dictionary<string, string>()
{
{ "webshoppingagg", "Shopping Aggregator for Web Clients" }
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var identityUrl = Configuration.GetValue<string>("urls:identity");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "webshoppingagg";
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = async ctx =>
{
int i = 0;
},
OnTokenValidated = async ctx =>
{
int i = 0;
}
};
});
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ {
var pathBase = Configuration["PATH_BASE"]; var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase)) if (!string.IsNullOrEmpty(pathBase))
{ {
@ -135,4 +65,113 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator
} }
} }
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var identityUrl = configuration.GetValue<string>("urls:identity");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "webshoppingagg";
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = async ctx =>
{
int i = 0;
},
OnTokenValidated = async ctx =>
{
int i = 0;
}
};
});
return services;
}
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions();
services.Configure<UrlsConfig>(configuration.GetSection("urls"));
services.AddMvc();
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "Shopping Aggregator for Web Clients",
Version = "v1",
Description = "Shopping Aggregator for Web Clients",
TermsOfService = "Terms Of Service"
});
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize",
TokenUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
Scopes = new Dictionary<string, string>()
{
{ "webshoppingagg", "Shopping Aggregator for Web Clients" }
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
return services;
}
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
//register delegating handlers
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//register http services
var retriesWithExponentialBackoff = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(7, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
var circuitBreaker = HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(6, TimeSpan.FromSeconds(30));
services.AddHttpClient<IBasketService, BasketService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
services.AddHttpClient<ICatalogService, CatalogService>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandler(retriesWithExponentialBackoff)
.AddPolicyHandler(circuitBreaker);
return services;
}
}
} }

View File

@ -15,6 +15,7 @@
<PackageReference Include="Microsoft.AspNetCore.App" version="2.1.0-rc1-final" /> <PackageReference Include="Microsoft.AspNetCore.App" version="2.1.0-rc1-final" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="2.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="2.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUi" Version="2.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUi" Version="2.4.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="2.1.0-rc1-final" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>