Browse Source

Updated codes from net21rc1 branch

pull/702/head
rafsanulhasan 6 years ago
parent
commit
49200d77b2
59 changed files with 1085 additions and 1041 deletions
  1. +2
    -2
      src/ApiGateways/ApiGw-Base/Dockerfile
  2. +3
    -3
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile
  3. +49
    -0
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs
  4. +12
    -24
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs
  5. +15
    -17
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs
  6. +14
    -14
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderApiClient.cs
  7. +0
    -24
      src/ApiGateways/Mobile.Bff.Shopping/apigw/configuration.json
  8. +1
    -1
      src/Services/Basket/Basket.API/Controllers/BasketController.cs
  9. +3
    -3
      src/Services/Basket/Basket.API/Dockerfile
  10. +2
    -1
      src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs
  11. +1
    -1
      src/Services/Catalog/Catalog.API/Controllers/PicController.cs
  12. +3
    -3
      src/Services/Catalog/Catalog.API/Dockerfile
  13. +8
    -3
      src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
  14. +5
    -5
      src/Services/Identity/Identity.API/AppSettings.cs
  15. +252
    -248
      src/Services/Identity/Identity.API/Configuration/Config.cs
  16. +216
    -216
      src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs
  17. +27
    -3
      src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs
  18. +17
    -3
      src/Services/Identity/Identity.API/Dockerfile
  19. +18
    -17
      src/Services/Identity/Identity.API/appsettings.json
  20. +3
    -3
      src/Services/Location/Locations.API/Dockerfile
  21. +3
    -3
      src/Services/Marketing/Marketing.API/Dockerfile
  22. +1
    -0
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs
  23. +2
    -1
      src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs
  24. +6
    -4
      src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs
  25. +3
    -3
      src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
  26. +3
    -3
      src/Services/Ordering/Ordering.API/Dockerfile
  27. +10
    -7
      src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
  28. +0
    -2
      src/Services/Ordering/Ordering.API/OrderingSettings.cs
  29. +19
    -20
      src/Services/Ordering/Ordering.API/settings.json
  30. +3
    -3
      src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile
  31. +5
    -7
      src/Services/Ordering/Ordering.SignalrHub/Dockerfile
  32. +2
    -2
      src/Services/Ordering/Ordering.SignalrHub/NotificationHub.cs
  33. +3
    -3
      src/Services/Payment/Payment.API/Dockerfile
  34. +1
    -2
      src/Web/WebMVC/AppSettings.cs
  35. +0
    -1
      src/Web/WebMVC/Controllers/CartController.cs
  36. +5
    -9
      src/Web/WebMVC/Controllers/OrderController.cs
  37. +16
    -11
      src/Web/WebMVC/Controllers/TestController.cs
  38. +16
    -3
      src/Web/WebMVC/Dockerfile
  39. +49
    -0
      src/Web/WebMVC/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs
  40. +26
    -0
      src/Web/WebMVC/Infrastructure/HttpClientRequestIdDelegatingHandler.cs
  41. +0
    -10
      src/Web/WebMVC/Infrastructure/IResilientHttpClientFactory.cs
  42. +0
    -66
      src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs
  43. +4
    -4
      src/Web/WebMVC/Infrastructure/WebContextSeed.cs
  44. +2
    -1
      src/Web/WebMVC/Properties/launchSettings.json
  45. +41
    -46
      src/Web/WebMVC/Services/BasketService.cs
  46. +12
    -32
      src/Web/WebMVC/Services/CampaignService.cs
  47. +17
    -15
      src/Web/WebMVC/Services/CatalogService.cs
  48. +30
    -31
      src/Web/WebMVC/Services/IdentityParser.cs
  49. +10
    -21
      src/Web/WebMVC/Services/LocationService.cs
  50. +39
    -53
      src/Web/WebMVC/Services/OrderingService.cs
  51. +23
    -22
      src/Web/WebMVC/ViewModels/Annotations/CardExpiration.cs
  52. +15
    -15
      src/Web/WebMVC/ViewModels/Annotations/LatitudeCoordinate.cs
  53. +15
    -15
      src/Web/WebMVC/ViewModels/Annotations/LongitudeCoordinate.cs
  54. +25
    -25
      src/Web/WebMVC/appsettings.json
  55. +15
    -3
      src/Web/WebSPA/Dockerfile
  56. +8
    -3
      src/Web/WebSPA/Properties/launchSettings.json
  57. +1
    -1
      src/Web/WebSPA/WebSPA.csproj
  58. +3
    -3
      src/Web/WebStatus/Dockerfile
  59. +1
    -0
      src/Web/WebStatus/appsettings.json

+ 2
- 2
src/ApiGateways/ApiGw-Base/Dockerfile View File

@ -1,8 +1,8 @@
FROM microsoft/aspnetcore:2.0 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj src/ApiGateways/ApiGw-Base/ COPY src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj src/ApiGateways/ApiGw-Base/
RUN dotnet restore src/ApiGateways/ApiGw-Base/ RUN dotnet restore src/ApiGateways/ApiGw-Base/


+ 3
- 3
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile View File

@ -1,12 +1,12 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/ApiGateways/Mobile.Bff.Shopping/aggregator WORKDIR /src/src/ApiGateways/Mobile.Bff.Shopping/aggregator
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 49
- 0
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs 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);
}
}
}

+ 12
- 24
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs View File

@ -1,53 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
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 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 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 _apiClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id), token);
var data = await _httpClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id));
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 data = await _apiClient.PostAsync<BasketData>(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), currentBasket, token);
int i = 0;
}
var basketContent = new StringContent(JsonConvert.SerializeObject(currentBasket), System.Text.Encoding.UTF8, "application/json");
async Task<string> GetUserTokenAsync()
{
var context = _httpContextAccessor.HttpContext;
return await context.GetTokenAsync("access_token");
var data = await _httpClient.PostAsync(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), basketContent);
} }
} }
} }

+ 15
- 17
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs View File

@ -1,43 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
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 Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
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 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 item = JsonConvert.DeserializeObject<CatalogItem>(data);
return item;
var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id));
var catalogItem = JsonConvert.DeserializeObject<CatalogItem>(stringContent);
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 item = JsonConvert.DeserializeObject<CatalogItem[]>(data);
return item;
var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids));
var catalogItems = JsonConvert.DeserializeObject<CatalogItem[]>(stringContent);
return catalogItems;
} }
} }
} }

+ 14
- 14
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderApiClient.cs View File

@ -1,24 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
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 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 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;
@ -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 response = await _apiClient.PostAsync<BasketData>(url, basket);
var uri = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft();
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);
} }
} }
} }

+ 0
- 24
src/ApiGateways/Mobile.Bff.Shopping/apigw/configuration.json View File

@ -96,18 +96,6 @@
"UpstreamPathTemplate": "/catalog-api/{everything}", "UpstreamPathTemplate": "/catalog-api/{everything}",
"UpstreamHttpMethod": [] "UpstreamHttpMethod": []
}, },
{
"DownstreamPathTemplate": "/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "marketing.api",
"Port": 80
}
],
"UpstreamPathTemplate": "/marketing-api/{everything}",
"UpstreamHttpMethod": []
},
{ {
"DownstreamPathTemplate": "/{everything}", "DownstreamPathTemplate": "/{everything}",
"DownstreamScheme": "http", "DownstreamScheme": "http",
@ -119,18 +107,6 @@
], ],
"UpstreamPathTemplate": "/payment-api/{everything}", "UpstreamPathTemplate": "/payment-api/{everything}",
"UpstreamHttpMethod": [] "UpstreamHttpMethod": []
},
{
"DownstreamPathTemplate": "/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "locations.api",
"Port": 80
}
],
"UpstreamPathTemplate": "/location-api/{everything}",
"UpstreamHttpMethod": []
} }
], ],


+ 1
- 1
src/Services/Basket/Basket.API/Controllers/BasketController.cs 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);


+ 3
- 3
src/Services/Basket/Basket.API/Dockerfile View File

@ -1,12 +1,12 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Services/Basket/Basket.API WORKDIR /src/src/Services/Basket/Basket.API
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 2
- 1
src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs View File

@ -15,6 +15,7 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{ {
[Route("api/v1/[controller]")] [Route("api/v1/[controller]")]
[ApiController]
public class CatalogController : ControllerBase public class CatalogController : ControllerBase
{ {
private readonly CatalogContext _catalogContext; private readonly CatalogContext _catalogContext;
@ -25,8 +26,8 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{ {
_catalogContext = context ?? throw new ArgumentNullException(nameof(context)); _catalogContext = context ?? throw new ArgumentNullException(nameof(context));
_catalogIntegrationEventService = catalogIntegrationEventService ?? throw new ArgumentNullException(nameof(catalogIntegrationEventService)); _catalogIntegrationEventService = catalogIntegrationEventService ?? throw new ArgumentNullException(nameof(catalogIntegrationEventService));
_settings = settings.Value; _settings = settings.Value;
((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; ((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
} }


+ 1
- 1
src/Services/Catalog/Catalog.API/Controllers/PicController.cs View File

@ -9,7 +9,7 @@ using System.Threading.Tasks;
// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{
{
public class PicController : Controller public class PicController : Controller
{ {
private readonly IHostingEnvironment _env; private readonly IHostingEnvironment _env;


+ 3
- 3
src/Services/Catalog/Catalog.API/Dockerfile View File

@ -1,12 +1,12 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Services/Catalog/Catalog.API WORKDIR /src/src/Services/Catalog/Catalog.API
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 8
- 3
src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs View File

@ -1,6 +1,7 @@
using Catalog.API.Infrastructure.ActionResults; using Catalog.API.Infrastructure.ActionResults;
using Catalog.API.Infrastructure.Exceptions; using Catalog.API.Infrastructure.Exceptions;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -27,12 +28,16 @@ namespace Catalog.API.Infrastructure.Filters
if (context.Exception.GetType() == typeof(CatalogDomainException)) if (context.Exception.GetType() == typeof(CatalogDomainException))
{ {
var json = new JsonErrorResponse
var problemDetails = new ValidationProblemDetails()
{ {
Messages = new[] { context.Exception.Message }
Instance = context.HttpContext.Request.Path,
Status = StatusCodes.Status400BadRequest,
Detail = "Please refer to the errors property for additional details."
}; };
context.Result = new BadRequestObjectResult(json);
problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() });
context.Result = new BadRequestObjectResult(problemDetails);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
} }
else else


+ 5
- 5
src/Services/Identity/Identity.API/AppSettings.cs View File

@ -1,9 +1,9 @@
namespace Microsoft.eShopOnContainers.Services.Identity.API namespace Microsoft.eShopOnContainers.Services.Identity.API
{ {
public class AppSettings
{
public string MvcClient { get; set; }
public class AppSettings
{
public string MvcClient { get; set; }
public bool UseCustomizationData { get; set; }
}
public bool UseCustomizationData { get; set; }
}
} }

+ 252
- 248
src/Services/Identity/Identity.API/Configuration/Config.cs View File

@ -4,252 +4,256 @@ using System.Collections.Generic;
namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
{ {
public class Config
{
// ApiResources define the apis in your system
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("orders", "Orders Service"),
new ApiResource("basket", "Basket Service"),
new ApiResource("marketing", "Marketing Service"),
new ApiResource("locations", "Locations Service"),
new ApiResource("mobileshoppingagg", "Mobile Shopping Aggregator"),
new ApiResource("webshoppingagg", "Web Shopping Aggregator"),
new ApiResource("orders.signalrhub", "Ordering Signalr Hub")
};
}
// Identity resources are data like user ID, name, or email address of a user
// see: http://docs.identityserver.io/en/release/configuration/resources.html
public static IEnumerable<IdentityResource> GetResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients(Dictionary<string,string> clientsUrl)
{
return new List<Client>
{
// JavaScript Client
new Client
{
ClientId = "js",
ClientName = "eShop SPA OpenId Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["Spa"]}/" },
RequireConsent = false,
PostLogoutRedirectUris = { $"{clientsUrl["Spa"]}/" },
AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"orders",
"basket",
"locations",
"marketing",
"webshoppingagg",
"orders.signalrhub"
}
},
new Client
{
ClientId = "xamarin",
ClientName = "eShop Xamarin OpenId Client",
AllowedGrantTypes = GrantTypes.Hybrid,
//Used to retrieve the access token on the back channel.
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { clientsUrl["Xamarin"] },
RequireConsent = false,
RequirePkce = true,
PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" },
AllowedCorsOrigins = { "http://eshopxamarin" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"orders",
"basket",
"locations",
"marketing",
"mobileshoppingagg"
},
//Allow requesting refresh tokens for long lived API access
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true
},
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = false,
RequireConsent = false,
AllowOfflineAccess = true,
AlwaysIncludeUserClaimsInIdToken = true,
RedirectUris = new List<string>
{
$"{clientsUrl["Mvc"]}/signin-oidc"
},
PostLogoutRedirectUris = new List<string>
{
$"{clientsUrl["Mvc"]}/signout-callback-oidc"
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"orders",
"basket",
"locations",
"marketing",
"webshoppingagg",
"orders.signalrhub"
},
},
new Client
{
ClientId = "mvctest",
ClientName = "MVC Client Test",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
AllowOfflineAccess = true,
RedirectUris = new List<string>
{
$"{clientsUrl["Mvc"]}/signin-oidc"
},
PostLogoutRedirectUris = new List<string>
{
$"{clientsUrl["Mvc"]}/signout-callback-oidc"
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"orders",
"basket",
"locations",
"marketing",
"webshoppingagg"
},
},
new Client
{
ClientId = "locationsswaggerui",
ClientName = "Locations Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["LocationsApi"]}/swagger/o2c.html" },
PostLogoutRedirectUris = { $"{clientsUrl["LocationsApi"]}/swagger/" },
AllowedScopes =
{
"locations"
}
},
new Client
{
ClientId = "marketingswaggerui",
ClientName = "Marketing Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["MarketingApi"]}/swagger/o2c.html" },
PostLogoutRedirectUris = { $"{clientsUrl["MarketingApi"]}/swagger/" },
AllowedScopes =
{
"marketing"
}
},
new Client
{
ClientId = "basketswaggerui",
ClientName = "Basket Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/o2c.html" },
PostLogoutRedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/" },
AllowedScopes =
{
"basket"
}
},
new Client
{
ClientId = "orderingswaggerui",
ClientName = "Ordering Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/o2c.html" },
PostLogoutRedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/" },
AllowedScopes =
{
"orders"
}
},
new Client
{
ClientId = "mobileshoppingaggswaggerui",
ClientName = "Mobile Shopping Aggregattor Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["MobileShoppingAgg"]}/swagger/o2c.html" },
PostLogoutRedirectUris = { $"{clientsUrl["MobileShoppingAgg"]}/swagger/" },
AllowedScopes =
{
"mobileshoppingagg"
}
},
new Client
{
ClientId = "webshoppingaggswaggerui",
ClientName = "Web Shopping Aggregattor Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/o2c.html" },
PostLogoutRedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/" },
AllowedScopes =
{
"webshoppingagg"
}
}
};
}
}
public class Config
{
// ApiResources define the apis in your system
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("orders", "Orders Service"),
new ApiResource("basket", "Basket Service"),
new ApiResource("marketing", "Marketing Service"),
new ApiResource("locations", "Locations Service"),
new ApiResource("mobileshoppingagg", "Mobile Shopping Aggregator"),
new ApiResource("webshoppingagg", "Web Shopping Aggregator"),
new ApiResource("orders.signalrhub", "Ordering SignalR Hub")
};
}
// Identity resources are data like user ID, name, or email address of a user
// see: http://docs.identityserver.io/en/release/configuration/resources.html
public static IEnumerable<IdentityResource> GetResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients(Dictionary<string, string> clientsUrl)
{
return new List<Client>
{
// JavaScript Client
new Client
{
ClientId = "js",
ClientName = "eShop SPA OpenId Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["Spa"]}/" },
RequireConsent = false,
PostLogoutRedirectUris = { $"{clientsUrl["Spa"]}/" },
AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"orders",
"basket",
"locations",
"marketing",
"webshoppingagg",
"orders.signalrhub"
}
},
new Client
{
ClientId = "xamarin",
ClientName = "eShop Xamarin OpenId Client",
AllowedGrantTypes = GrantTypes.Hybrid,
//Used to retrieve the access token on the back channel.
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { clientsUrl["Xamarin"] },
RequireConsent = false,
RequirePkce = true,
PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" },
AllowedCorsOrigins = { "http://eshopxamarin" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"orders",
"basket",
"locations",
"marketing",
"mobileshoppingagg"
},
//Allow requesting refresh tokens for long lived API access
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true
},
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = false,
RequireConsent = false,
AllowOfflineAccess = true,
AlwaysIncludeUserClaimsInIdToken = true,
RedirectUris = new List<string>
{
$"{clientsUrl["Mvc"]}/signin-oidc"
},
PostLogoutRedirectUris = new List<string>
{
$"{clientsUrl["Mvc"]}/signout-callback-oidc"
},
AllowedCorsOrigins = new List<string>
{
$"{clientsUrl["Mvc"]}"
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"orders",
"basket",
"locations",
"marketing",
"webshoppingagg",
"orders.signalrhub"
},
},
new Client
{
ClientId = "mvctest",
ClientName = "MVC Client Test",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
AllowOfflineAccess = true,
RedirectUris = new List<string>
{
$"{clientsUrl["Mvc"]}/signin-oidc"
},
PostLogoutRedirectUris = new List<string>
{
$"{clientsUrl["Mvc"]}/signout-callback-oidc"
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"orders",
"basket",
"locations",
"marketing",
"webshoppingagg"
},
},
new Client
{
ClientId = "locationsswaggerui",
ClientName = "Locations Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["LocationsApi"]}/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { $"{clientsUrl["LocationsApi"]}/swagger/" },
AllowedScopes =
{
"locations"
}
},
new Client
{
ClientId = "marketingswaggerui",
ClientName = "Marketing Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["MarketingApi"]}/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { $"{clientsUrl["MarketingApi"]}/swagger/" },
AllowedScopes =
{
"marketing"
}
},
new Client
{
ClientId = "basketswaggerui",
ClientName = "Basket Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/" },
AllowedScopes =
{
"basket"
}
},
new Client
{
ClientId = "orderingswaggerui",
ClientName = "Ordering Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/" },
AllowedScopes =
{
"orders"
}
},
new Client
{
ClientId = "mobileshoppingaggswaggerui",
ClientName = "Mobile Shopping Aggregattor Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["MobileShoppingAgg"]}/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { $"{clientsUrl["MobileShoppingAgg"]}/swagger/" },
AllowedScopes =
{
"mobileshoppingagg"
}
},
new Client
{
ClientId = "webshoppingaggswaggerui",
ClientName = "Web Shopping Aggregattor Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/" },
AllowedScopes =
{
"webshoppingagg"
}
}
};
}
}
} }

+ 216
- 216
src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs View File

@ -16,220 +16,220 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
{ {
public class ApplicationDbContextSeed
{
private readonly IPasswordHasher<ApplicationUser> _passwordHasher = new PasswordHasher<ApplicationUser>();
public async Task SeedAsync(ApplicationDbContext context,IHostingEnvironment env,
ILogger<ApplicationDbContextSeed> logger, IOptions<AppSettings> settings,int? retry = 0)
{
int retryForAvaiability = retry.Value;
try
{
var useCustomizationData = settings.Value.UseCustomizationData;
var contentRootPath = env.ContentRootPath;
var webroot = env.WebRootPath;
if (!context.Users.Any())
{
context.Users.AddRange(useCustomizationData
? GetUsersFromFile(contentRootPath, logger)
: GetDefaultUser());
await context.SaveChangesAsync();
}
if (useCustomizationData)
{
GetPreconfiguredImages(contentRootPath, webroot, logger);
}
}
catch (Exception ex)
{
if (retryForAvaiability < 10)
{
retryForAvaiability++;
logger.LogError(ex.Message,$"There is an error migrating data for ApplicationDbContext");
await SeedAsync(context,env,logger,settings, retryForAvaiability);
}
}
}
private IEnumerable<ApplicationUser> GetUsersFromFile(string contentRootPath, ILogger logger)
{
string csvFileUsers = Path.Combine(contentRootPath, "Setup", "Users.csv");
if (!File.Exists(csvFileUsers))
{
return GetDefaultUser();
}
string[] csvheaders;
try
{
string[] requiredHeaders = {
"cardholdername", "cardnumber", "cardtype", "city", "country",
"email", "expiration", "lastname", "name", "phonenumber",
"username", "zipcode", "state", "street", "securitynumber",
"normalizedemail", "normalizedusername", "password"
};
csvheaders = GetHeaders(requiredHeaders, csvFileUsers);
}
catch (Exception ex)
{
logger.LogError(ex.Message);
return GetDefaultUser();
}
List<ApplicationUser> users = File.ReadAllLines(csvFileUsers)
.Skip(1) // skip header column
.Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)") )
.SelectTry(column => CreateApplicationUser(column, csvheaders))
.OnCaughtException(ex => { logger.LogError(ex.Message); return null; })
.Where(x => x != null)
.ToList();
return users;
}
private ApplicationUser CreateApplicationUser(string[] column, string[] headers)
{
if (column.Count() != headers.Count())
{
throw new Exception($"column count '{column.Count()}' not the same as headers count'{headers.Count()}'");
}
string cardtypeString = column[Array.IndexOf(headers, "cardtype")].Trim('"').Trim();
if (!int.TryParse(cardtypeString, out int cardtype))
{
throw new Exception($"cardtype='{cardtypeString}' is not a number");
}
var user = new ApplicationUser
{
CardHolderName = column[Array.IndexOf(headers, "cardholdername")].Trim('"').Trim(),
CardNumber = column[Array.IndexOf(headers, "cardnumber")].Trim('"').Trim(),
CardType = cardtype,
City = column[Array.IndexOf(headers, "city")].Trim('"').Trim(),
Country = column[Array.IndexOf(headers, "country")].Trim('"').Trim(),
Email = column[Array.IndexOf(headers, "email")].Trim('"').Trim(),
Expiration = column[Array.IndexOf(headers, "expiration")].Trim('"').Trim(),
Id = Guid.NewGuid().ToString(),
LastName = column[Array.IndexOf(headers, "lastname")].Trim('"').Trim(),
Name = column[Array.IndexOf(headers, "name")].Trim('"').Trim(),
PhoneNumber = column[Array.IndexOf(headers, "phonenumber")].Trim('"').Trim(),
UserName = column[Array.IndexOf(headers, "username")].Trim('"').Trim(),
ZipCode = column[Array.IndexOf(headers, "zipcode")].Trim('"').Trim(),
State = column[Array.IndexOf(headers, "state")].Trim('"').Trim(),
Street = column[Array.IndexOf(headers, "street")].Trim('"').Trim(),
SecurityNumber = column[Array.IndexOf(headers, "securitynumber")].Trim('"').Trim(),
NormalizedEmail = column[Array.IndexOf(headers, "normalizedemail")].Trim('"').Trim(),
NormalizedUserName = column[Array.IndexOf(headers, "normalizedusername")].Trim('"').Trim(),
SecurityStamp = Guid.NewGuid().ToString("D"),
PasswordHash = column[Array.IndexOf(headers, "password")].Trim('"').Trim(), // Note: This is the password
};
user.PasswordHash = _passwordHasher.HashPassword(user, user.PasswordHash);
return user;
}
private IEnumerable<ApplicationUser> GetDefaultUser()
{
var user =
new ApplicationUser()
{
CardHolderName = "DemoUser",
CardNumber = "4012888888881881",
CardType = 1,
City = "Redmond",
Country = "U.S.",
Email = "demouser@microsoft.com",
Expiration = "12/20",
Id = Guid.NewGuid().ToString(),
LastName = "DemoLastName",
Name = "DemoUser",
PhoneNumber = "1234567890",
UserName = "demouser@microsoft.com",
ZipCode = "98052",
State = "WA",
Street = "15703 NE 61st Ct",
SecurityNumber = "535",
NormalizedEmail = "DEMOUSER@MICROSOFT.COM",
NormalizedUserName = "DEMOUSER@MICROSOFT.COM",
SecurityStamp = Guid.NewGuid().ToString("D"),
};
user.PasswordHash = _passwordHasher.HashPassword(user, "Pass@word1");
return new List<ApplicationUser>()
{
user
};
}
static string[] GetHeaders(string[] requiredHeaders, string csvfile)
{
string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(',');
if (csvheaders.Count() != requiredHeaders.Count())
{
throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'");
}
foreach (var requiredHeader in requiredHeaders)
{
if (!csvheaders.Contains(requiredHeader))
{
throw new Exception($"does not contain required header '{requiredHeader}'");
}
}
return csvheaders;
}
static void GetPreconfiguredImages(string contentRootPath, string webroot, ILogger logger)
{
try
{
string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip");
if (!File.Exists(imagesZipFile))
{
logger.LogError($" zip file '{imagesZipFile}' does not exists.");
return;
}
string imagePath = Path.Combine(webroot, "images");
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in zip.Entries)
{
if (imageFiles.Contains(entry.Name))
{
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
logger.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'");
}
}
}
}
catch (Exception ex)
{
logger.LogError($"Exception in method GetPreconfiguredImages WebMVC. Exception Message={ex.Message}");
}
}
}
public class ApplicationDbContextSeed
{
private readonly IPasswordHasher<ApplicationUser> _passwordHasher = new PasswordHasher<ApplicationUser>();
public async Task SeedAsync(ApplicationDbContext context, IHostingEnvironment env,
ILogger<ApplicationDbContextSeed> logger, IOptions<AppSettings> settings, int? retry = 0)
{
int retryForAvaiability = retry.Value;
try
{
var useCustomizationData = settings.Value.UseCustomizationData;
var contentRootPath = env.ContentRootPath;
var webroot = env.WebRootPath;
if (!context.Users.Any())
{
context.Users.AddRange(useCustomizationData
? GetUsersFromFile(contentRootPath, logger)
: GetDefaultUser());
await context.SaveChangesAsync();
}
if (useCustomizationData)
{
GetPreconfiguredImages(contentRootPath, webroot, logger);
}
}
catch (Exception ex)
{
if (retryForAvaiability < 10)
{
retryForAvaiability++;
logger.LogError(ex.Message, $"There is an error migrating data for ApplicationDbContext");
await SeedAsync(context, env, logger, settings, retryForAvaiability);
}
}
}
private IEnumerable<ApplicationUser> GetUsersFromFile(string contentRootPath, ILogger logger)
{
string csvFileUsers = Path.Combine(contentRootPath, "Setup", "Users.csv");
if (!File.Exists(csvFileUsers))
{
return GetDefaultUser();
}
string[] csvheaders;
try
{
string[] requiredHeaders = {
"cardholdername", "cardnumber", "cardtype", "city", "country",
"email", "expiration", "lastname", "name", "phonenumber",
"username", "zipcode", "state", "street", "securitynumber",
"normalizedemail", "normalizedusername", "password"
};
csvheaders = GetHeaders(requiredHeaders, csvFileUsers);
}
catch (Exception ex)
{
logger.LogError(ex.Message);
return GetDefaultUser();
}
List<ApplicationUser> users = File.ReadAllLines(csvFileUsers)
.Skip(1) // skip header column
.Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"))
.SelectTry(column => CreateApplicationUser(column, csvheaders))
.OnCaughtException(ex => { logger.LogError(ex.Message); return null; })
.Where(x => x != null)
.ToList();
return users;
}
private ApplicationUser CreateApplicationUser(string[] column, string[] headers)
{
if (column.Count() != headers.Count())
{
throw new Exception($"column count '{column.Count()}' not the same as headers count'{headers.Count()}'");
}
string cardtypeString = column[Array.IndexOf(headers, "cardtype")].Trim('"').Trim();
if (!int.TryParse(cardtypeString, out int cardtype))
{
throw new Exception($"cardtype='{cardtypeString}' is not a number");
}
var user = new ApplicationUser
{
CardHolderName = column[Array.IndexOf(headers, "cardholdername")].Trim('"').Trim(),
CardNumber = column[Array.IndexOf(headers, "cardnumber")].Trim('"').Trim(),
CardType = cardtype,
City = column[Array.IndexOf(headers, "city")].Trim('"').Trim(),
Country = column[Array.IndexOf(headers, "country")].Trim('"').Trim(),
Email = column[Array.IndexOf(headers, "email")].Trim('"').Trim(),
Expiration = column[Array.IndexOf(headers, "expiration")].Trim('"').Trim(),
Id = Guid.NewGuid().ToString(),
LastName = column[Array.IndexOf(headers, "lastname")].Trim('"').Trim(),
Name = column[Array.IndexOf(headers, "name")].Trim('"').Trim(),
PhoneNumber = column[Array.IndexOf(headers, "phonenumber")].Trim('"').Trim(),
UserName = column[Array.IndexOf(headers, "username")].Trim('"').Trim(),
ZipCode = column[Array.IndexOf(headers, "zipcode")].Trim('"').Trim(),
State = column[Array.IndexOf(headers, "state")].Trim('"').Trim(),
Street = column[Array.IndexOf(headers, "street")].Trim('"').Trim(),
SecurityNumber = column[Array.IndexOf(headers, "securitynumber")].Trim('"').Trim(),
NormalizedEmail = column[Array.IndexOf(headers, "normalizedemail")].Trim('"').Trim(),
NormalizedUserName = column[Array.IndexOf(headers, "normalizedusername")].Trim('"').Trim(),
SecurityStamp = Guid.NewGuid().ToString("D"),
PasswordHash = column[Array.IndexOf(headers, "password")].Trim('"').Trim(), // Note: This is the password
};
user.PasswordHash = _passwordHasher.HashPassword(user, user.PasswordHash);
return user;
}
private IEnumerable<ApplicationUser> GetDefaultUser()
{
var user =
new ApplicationUser()
{
CardHolderName = "DemoUser",
CardNumber = "4012888888881881",
CardType = 1,
City = "Redmond",
Country = "U.S.",
Email = "demouser@microsoft.com",
Expiration = "12/20",
Id = Guid.NewGuid().ToString(),
LastName = "DemoLastName",
Name = "DemoUser",
PhoneNumber = "1234567890",
UserName = "demouser@microsoft.com",
ZipCode = "98052",
State = "WA",
Street = "15703 NE 61st Ct",
SecurityNumber = "535",
NormalizedEmail = "DEMOUSER@MICROSOFT.COM",
NormalizedUserName = "DEMOUSER@MICROSOFT.COM",
SecurityStamp = Guid.NewGuid().ToString("D"),
};
user.PasswordHash = _passwordHasher.HashPassword(user, "Pass@word1");
return new List<ApplicationUser>()
{
user
};
}
static string[] GetHeaders(string[] requiredHeaders, string csvfile)
{
string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(',');
if (csvheaders.Count() != requiredHeaders.Count())
{
throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'");
}
foreach (var requiredHeader in requiredHeaders)
{
if (!csvheaders.Contains(requiredHeader))
{
throw new Exception($"does not contain required header '{requiredHeader}'");
}
}
return csvheaders;
}
static void GetPreconfiguredImages(string contentRootPath, string webroot, ILogger logger)
{
try
{
string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip");
if (!File.Exists(imagesZipFile))
{
logger.LogError($" zip file '{imagesZipFile}' does not exists.");
return;
}
string imagePath = Path.Combine(webroot, "images");
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in zip.Entries)
{
if (imageFiles.Contains(entry.Name))
{
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
logger.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'");
}
}
}
}
catch (Exception ex)
{
logger.LogError($"Exception in method GetPreconfiguredImages WebMVC. Exception Message={ex.Message}");
}
}
}
} }

+ 27
- 3
src/Services/Identity/Identity.API/Data/ConfigurationDbContextSeed.cs View File

@ -1,7 +1,10 @@
using IdentityServer4.EntityFramework.DbContexts; using IdentityServer4.EntityFramework.DbContexts;
using IdentityServer4.EntityFramework.Entities;
using IdentityServer4.EntityFramework.Mappers; using IdentityServer4.EntityFramework.Mappers;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.Services.Identity.API.Configuration; using Microsoft.eShopOnContainers.Services.Identity.API.Configuration;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -30,16 +33,37 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
{ {
foreach (var client in Config.GetClients(clientUrls)) foreach (var client in Config.GetClients(clientUrls))
{ {
await context.Clients.AddAsync(client.ToEntity());
context.Clients.Add(client.ToEntity());
} }
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
// Checking always for old redirects to fix existing deployments
// to use new swagger-ui redirect uri as of v3.0.0
// There should be no problem for new ones
// ref: https://github.com/dotnet-architecture/eShopOnContainers/issues/586
else
{
List<ClientRedirectUri> oldRedirects = (await context.Clients.Include(c => c.RedirectUris).ToListAsync())
.SelectMany(c => c.RedirectUris)
.Where(ru => ru.RedirectUri.EndsWith("/o2c.html"))
.ToList();
if (oldRedirects.Any())
{
foreach (var ru in oldRedirects)
{
ru.RedirectUri = ru.RedirectUri.Replace("/o2c.html", "/oauth2-redirect.html");
context.Update(ru.Client);
}
await context.SaveChangesAsync();
}
}
if (!context.IdentityResources.Any()) if (!context.IdentityResources.Any())
{ {
foreach (var resource in Config.GetResources()) foreach (var resource in Config.GetResources())
{ {
await context.IdentityResources.AddAsync(resource.ToEntity());
context.IdentityResources.Add(resource.ToEntity());
} }
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
@ -48,7 +72,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
{ {
foreach (var api in Config.GetApis()) foreach (var api in Config.GetApis())
{ {
await context.ApiResources.AddAsync(api.ToEntity());
context.ApiResources.Add(api.ToEntity());
} }
await context.SaveChangesAsync(); await context.SaveChangesAsync();


+ 17
- 3
src/Services/Identity/Identity.API/Dockerfile View File

@ -1,12 +1,26 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS sdk-with-node
ENV NODE_VERSION 8.11.1
ENV NODE_DOWNLOAD_SHA 0e20787e2eda4cc31336d8327556ebc7417e8ee0a6ba0de96a09b0ec2b841f60
RUN curl -SL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" --output nodejs.tar.gz \
&& echo "$NODE_DOWNLOAD_SHA nodejs.tar.gz" | sha256sum -c - \
&& tar -xzf "nodejs.tar.gz" -C /usr/local --strip-components=1 \
&& rm nodejs.tar.gz \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
FROM sdk-with-node AS updated-npm
RUN npm i -g npm
FROM updated-npm as build
RUN npm install -g bower@1.8.4
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Services/Identity/Identity.API WORKDIR /src/src/Services/Identity/Identity.API
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 18
- 17
src/Services/Identity/Identity.API/appsettings.json View File

@ -1,19 +1,20 @@
{ {
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.IdentityDb;User Id=sa;Password=Pass@word;",
"IsClusterEnv": "False",
"MvcClient": "http://localhost:5100",
"SpaClient": "http://localhost:5104",
"XamarinCallback": "http://localhost:5105/xamarincallback",
"UseCustomizationData": false,
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}
},
"ApplicationInsights": {
"InstrumentationKey": ""
}
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.IdentityDb;User Id=sa;Password=Pass@word;",
"IsClusterEnv": "False",
"IdentityServer": "http://localhost:5105",
"MvcClient": "http://localhost:5100",
"SpaClient": "http://localhost:5104",
"XamarinCallback": "http://localhost:5105/xamarincallback",
"UseCustomizationData": false,
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}
},
"ApplicationInsights": {
"InstrumentationKey": ""
}
} }

+ 3
- 3
src/Services/Location/Locations.API/Dockerfile View File

@ -1,12 +1,12 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Services/Location/Locations.API WORKDIR /src/src/Services/Location/Locations.API
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 3
- 3
src/Services/Marketing/Marketing.API/Dockerfile View File

@ -1,12 +1,12 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Services/Marketing/Marketing.API WORKDIR /src/src/Services/Marketing/Marketing.API
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 1
- 0
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs View File

@ -28,6 +28,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderCancelled
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository)); _buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository));
_orderingIntegrationEventService = orderingIntegrationEventService;
} }
public async Task Handle(OrderCancelledDomainEvent orderCancelledDomainEvent, CancellationToken cancellationToken) public async Task Handle(OrderCancelledDomainEvent orderCancelledDomainEvent, CancellationToken cancellationToken)


+ 2
- 1
src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs View File

@ -1,5 +1,6 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries
{ {
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,7 +8,7 @@
{ {
Task<Order> GetOrderAsync(int id); Task<Order> GetOrderAsync(int id);
Task<IEnumerable<OrderSummary>> GetOrdersAsync();
Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(Guid userId);
Task<IEnumerable<CardType>> GetCardTypesAsync(); Task<IEnumerable<CardType>> GetCardTypesAsync();
} }


+ 6
- 4
src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs View File

@ -7,7 +7,7 @@
using System.Collections.Generic; using System.Collections.Generic;
public class OrderQueries public class OrderQueries
:IOrderQueries
: IOrderQueries
{ {
private string _connectionString = string.Empty; private string _connectionString = string.Empty;
@ -42,18 +42,20 @@
} }
} }
public async Task<IEnumerable<OrderSummary>> GetOrdersAsync()
public async Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(Guid userId)
{ {
using (var connection = new SqlConnection(_connectionString)) using (var connection = new SqlConnection(_connectionString))
{ {
connection.Open(); connection.Open();
return await connection.QueryAsync<OrderSummary>(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status],SUM(oi.units*oi.unitprice) as total
return await connection.QueryAsync<OrderSummary>(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status], SUM(oi.units*oi.unitprice) as total
FROM [ordering].[Orders] o FROM [ordering].[Orders] o
LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid
LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id
LEFT JOIN[ordering].[buyers] ob on o.BuyerId = ob.Id
WHERE ob.IdentityGuid = @userId
GROUP BY o.[Id], o.[OrderDate], os.[Name] GROUP BY o.[Id], o.[OrderDate], os.[Name]
ORDER BY o.[Id]");
ORDER BY o.[Id]", new { userId });
} }
} }


+ 3
- 3
src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs View File

@ -5,7 +5,6 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
using Ordering.API.Application.Commands; using Ordering.API.Application.Commands;
using Ordering.API.Application.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
@ -15,6 +14,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
{ {
[Route("api/v1/[controller]")] [Route("api/v1/[controller]")]
[Authorize] [Authorize]
[ApiController]
public class OrdersController : Controller public class OrdersController : Controller
{ {
private readonly IMediator _mediator; private readonly IMediator _mediator;
@ -87,8 +87,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[ProducesResponseType(typeof(IEnumerable<OrderSummary>), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(IEnumerable<OrderSummary>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetOrders() public async Task<IActionResult> GetOrders()
{ {
var orders = await _orderQueries.GetOrdersAsync();
var userid = _identityService.GetUserIdentity();
var orders = await _orderQueries.GetOrdersFromUserAsync(Guid.Parse(userid));
return Ok(orders); return Ok(orders);
} }


+ 3
- 3
src/Services/Ordering/Ordering.API/Dockerfile View File

@ -1,12 +1,12 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Services/Ordering/Ordering.API WORKDIR /src/src/Services/Ordering/Ordering.API
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 10
- 7
src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs View File

@ -3,6 +3,7 @@
using AspNetCore.Mvc; using AspNetCore.Mvc;
using global::Ordering.Domain.Exceptions; using global::Ordering.Domain.Exceptions;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.ActionResults; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.ActionResults;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -25,16 +26,18 @@
context.Exception, context.Exception,
context.Exception.Message); context.Exception.Message);
if (context.Exception.GetType() == typeof(OrderingDomainException))
if (context.Exception.GetType() == typeof(OrderingDomainException))
{ {
var json = new JsonErrorResponse
var problemDetails = new ValidationProblemDetails()
{ {
Messages = new[] { context.Exception.Message }
Instance = context.HttpContext.Request.Path,
Status = StatusCodes.Status400BadRequest,
Detail = "Please refer to the errors property for additional details."
}; };
// Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1
//It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information
context.Result = new BadRequestObjectResult(json);
problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() });
context.Result = new BadRequestObjectResult(problemDetails);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
} }
else else
@ -52,7 +55,7 @@
// Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1 // Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1
// It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information // It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information
context.Result = new InternalServerErrorObjectResult(json); context.Result = new InternalServerErrorObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
} }
context.ExceptionHandled = true; context.ExceptionHandled = true;
} }


+ 0
- 2
src/Services/Ordering/Ordering.API/OrderingSettings.cs View File

@ -7,8 +7,6 @@
public string EventBusConnection { get; set; } public string EventBusConnection { get; set; }
public int GracePeriodTime { get; set; }
public int CheckUpdateTime { get; set; } public int CheckUpdateTime { get; set; }
} }
} }

+ 19
- 20
src/Services/Ordering/Ordering.API/settings.json View File

@ -1,22 +1,21 @@
{ {
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
"IdentityUrl": "http://localhost:5105",
"UseCustomizationData": false,
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}
},
"AzureServiceBusEnabled": false,
"SubscriptionClientName": "Ordering",
"GracePeriodTime": "1",
"CheckUpdateTime": "30000",
"ApplicationInsights": {
"InstrumentationKey": ""
},
"EventBusRetryCount": 5,
"EventBusConnection": "localhost"
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
"IdentityUrl": "http://localhost:5105",
"UseCustomizationData": false,
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}
},
"AzureServiceBusEnabled": false,
"SubscriptionClientName": "Ordering",
"CheckUpdateTime": "30000",
"ApplicationInsights": {
"InstrumentationKey": ""
},
"EventBusRetryCount": 5,
"EventBusConnection": "localhost"
} }

+ 3
- 3
src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile View File

@ -1,12 +1,12 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Services/Ordering/Ordering.BackgroundTasks WORKDIR /src/src/Services/Ordering/Ordering.BackgroundTasks
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 5
- 7
src/Services/Ordering/Ordering.SignalrHub/Dockerfile View File

@ -1,18 +1,16 @@
FROM microsoft/aspnetcore:2.0 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY eShopOnContainers-ServicesAndWebApps.sln ./
COPY src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj src/Services/Ordering/Ordering.SignalrHub/
RUN dotnet restore -nowarn:msb3202,nu1503
COPY . . COPY . .
WORKDIR /src/src/Services/Ordering/Ordering.SignalrHub WORKDIR /src/src/Services/Ordering/Ordering.SignalrHub
RUN dotnet build Ordering.SignalrHub.csproj -c Release -o /app
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore Ordering.SignalrHub.csproj -c Release -o /app
FROM build AS publish FROM build AS publish
RUN dotnet publish Ordering.SignalrHub.csproj -c Release -o /app
RUN dotnet publish --no-restore Ordering.SignalrHub.csproj -c Release -o /app
FROM base AS final FROM base AS final
WORKDIR /app WORKDIR /app


+ 2
- 2
src/Services/Ordering/Ordering.SignalrHub/NotificationHub.cs View File

@ -13,13 +13,13 @@ namespace Ordering.SignalrHub
public override async Task OnConnectedAsync() public override async Task OnConnectedAsync()
{ {
await Groups.AddAsync(Context.ConnectionId, Context.User.Identity.Name);
await Groups.AddToGroupAsync(Context.ConnectionId, Context.User.Identity.Name);
await base.OnConnectedAsync(); await base.OnConnectedAsync();
} }
public override async Task OnDisconnectedAsync(Exception ex) public override async Task OnDisconnectedAsync(Exception ex)
{ {
await Groups.RemoveAsync(Context.ConnectionId, Context.User.Identity.Name);
await Groups.AddToGroupAsync(Context.ConnectionId, Context.User.Identity.Name);
await base.OnDisconnectedAsync(ex); await base.OnDisconnectedAsync(ex);
} }
} }


+ 3
- 3
src/Services/Payment/Payment.API/Dockerfile View File

@ -1,12 +1,12 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Services/Payment/Payment.API WORKDIR /src/src/Services/Payment/Payment.API
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 1
- 2
src/Web/WebMVC/AppSettings.cs 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; }


+ 0
- 1
src/Web/WebMVC/Controllers/CartController.cs View File

@ -71,7 +71,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{ {
var user = _appUserParser.Parse(HttpContext.User); var user = _appUserParser.Parse(HttpContext.User);
await _basketSvc.AddItemToBasket(user, productDetails.Id); await _basketSvc.AddItemToBasket(user, productDetails.Id);
//await _basketSvc.AddItemToBasket(user, product);
} }
return RedirectToAction("Index", "Catalog"); return RedirectToAction("Index", "Catalog");
} }


+ 5
- 9
src/Web/WebMVC/Controllers/OrderController.cs View File

@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Authorization;
using System.Net.Http;
using Polly.CircuitBreaker; using Polly.CircuitBreaker;
using WebMVC.Models;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Controllers namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{ {
@ -52,11 +47,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
} }
catch(BrokenCircuitException)
catch (BrokenCircuitException)
{ {
ModelState.AddModelError("Error", "It was not possible to create a new order, please try later on. (Business Msg Due to Circuit-Breaker)"); ModelState.AddModelError("Error", "It was not possible to create a new order, please try later on. (Business Msg Due to Circuit-Breaker)");
} }
return View("Create", model);
return View("Create", model);
} }
public async Task<IActionResult> Cancel(string orderId) public async Task<IActionResult> Cancel(string orderId)


+ 16
- 11
src/Web/WebMVC/Controllers/TestController.cs View File

@ -1,12 +1,9 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace WebMVC.Controllers namespace WebMVC.Controllers
@ -14,6 +11,7 @@ namespace WebMVC.Controllers
class TestPayload class TestPayload
{ {
public int CatalogItemId { get; set; } public int CatalogItemId { get; set; }
public string BasketId { get; set; } public string BasketId { get; set; }
public int Quantity { get; set; } public int Quantity { get; set; }
@ -22,9 +20,10 @@ namespace WebMVC.Controllers
[Authorize] [Authorize]
public class TestController : Controller public class TestController : Controller
{ {
private readonly IHttpClient _client;
private readonly IHttpClientFactory _client;
private readonly IIdentityParser<ApplicationUser> _appUserParser; private readonly IIdentityParser<ApplicationUser> _appUserParser;
public TestController(IHttpClient client, IIdentityParser<ApplicationUser> identityParser)
public TestController(IHttpClientFactory client, IIdentityParser<ApplicationUser> identityParser)
{ {
_client = client; _client = client;
_appUserParser = identityParser; _appUserParser = identityParser;
@ -33,18 +32,24 @@ namespace WebMVC.Controllers
public async Task<IActionResult> Ocelot() public async Task<IActionResult> Ocelot()
{ {
var url = "http://apigw/shopping/api/v1/basket/items"; var url = "http://apigw/shopping/api/v1/basket/items";
var payload = new TestPayload() var payload = new TestPayload()
{ {
CatalogItemId = 1, CatalogItemId = 1,
Quantity = 1, Quantity = 1,
BasketId = _appUserParser.Parse(User).Id BasketId = _appUserParser.Parse(User).Id
}; };
var token = await HttpContext.GetTokenAsync("access_token");
var response = await _client.PostAsync<TestPayload>(url, payload, token);
var content = new StringContent(JsonConvert.SerializeObject(payload), System.Text.Encoding.UTF8, "application/json");
var response = await _client.CreateClient(nameof(IBasketService))
.PostAsync(url, content);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
var str = await response.Content.ReadAsStringAsync(); var str = await response.Content.ReadAsStringAsync();
return Ok(str); return Ok(str);
} }
else else


+ 16
- 3
src/Web/WebMVC/Dockerfile View File

@ -1,12 +1,25 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS sdk-with-node
ENV NODE_VERSION 8.11.1
ENV NODE_DOWNLOAD_SHA 0e20787e2eda4cc31336d8327556ebc7417e8ee0a6ba0de96a09b0ec2b841f60
RUN curl -SL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" --output nodejs.tar.gz \
&& echo "$NODE_DOWNLOAD_SHA nodejs.tar.gz" | sha256sum -c - \
&& tar -xzf "nodejs.tar.gz" -C /usr/local --strip-components=1 \
&& rm nodejs.tar.gz \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
FROM sdk-with-node AS updated-npm
RUN npm i -g npm
FROM updated-npm as build
RUN npm install -g bower@1.8.4
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Web/WebMVC WORKDIR /src/src/Web/WebMVC
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 49
- 0
src/Web/WebMVC/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs 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 WebMVC.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);
}
}
}

+ 26
- 0
src/Web/WebMVC/Infrastructure/HttpClientRequestIdDelegatingHandler.cs 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);
}
}
}

+ 0
- 10
src/Web/WebMVC/Infrastructure/IResilientHttpClientFactory.cs View File

@ -1,10 +0,0 @@
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using System;
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
{
public interface IResilientHttpClientFactory
{
ResilientHttpClient CreateResilientHttpClient();
}
}

+ 0
- 66
src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs View File

@ -1,66 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging;
using Polly;
using System;
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 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);
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.ExecutionKey}, " +
$"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");
})
};
}
}

+ 4
- 4
src/Web/WebMVC/Infrastructure/WebContextSeed.cs View File

@ -17,8 +17,8 @@ namespace WebMVC.Infrastructure
{ {
var log = loggerFactory.CreateLogger("WebMVC seed"); var log = loggerFactory.CreateLogger("WebMVC seed");
var settings = (AppSettings)applicationBuilder
.ApplicationServices.GetRequiredService<IOptions<AppSettings>>().Value;
var settings = applicationBuilder
.ApplicationServices.GetRequiredService<IOptions<AppSettings>>().Value;
var useCustomizationData = settings.UseCustomizationData; var useCustomizationData = settings.UseCustomizationData;
var contentRootPath = env.ContentRootPath; var contentRootPath = env.ContentRootPath;
@ -66,9 +66,9 @@ namespace WebMVC.Infrastructure
string imagePath = Path.Combine(webroot, "images"); string imagePath = Path.Combine(webroot, "images");
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray(); string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
using (var zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
{ {
foreach (ZipArchiveEntry entry in zip.Entries)
foreach (var entry in zip.Entries)
{ {
if (imageFiles.Contains(entry.Name)) if (imageFiles.Contains(entry.Name))
{ {


+ 2
- 1
src/Web/WebMVC/Properties/launchSettings.json View File

@ -12,7 +12,8 @@
"commandName": "IISExpress", "commandName": "IISExpress",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}
},
"use64Bit": true
}, },
"Microsoft.eShopOnContainers.WebMVC": { "Microsoft.eShopOnContainers.WebMVC": {
"commandName": "Project", "commandName": "Project",


+ 41
- 46
src/Web/WebMVC/Services/BasketService.cs 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 Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
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,42 +12,40 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
public class BasketService : IBasketService public class BasketService : IBasketService
{ {
private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IHttpClient _apiClient;
private readonly IOptions<AppSettings> _settings;
private readonly HttpClient _apiClient;
private readonly string _basketByPassUrl; private readonly string _basketByPassUrl;
private readonly string _purchaseUrl; private readonly string _purchaseUrl;
private readonly IHttpContextAccessor _httpContextAccesor;
private readonly string _bffUrl; private readonly string _bffUrl;
public BasketService(IOptionsSnapshot<AppSettings> settings,
IHttpContextAccessor httpContextAccesor, IHttpClient httpClient)
public BasketService(HttpClient httpClient, IOptions<AppSettings> settings)
{ {
_apiClient = httpClient;
_settings = settings; _settings = settings;
_basketByPassUrl = $"{_settings.Value.PurchaseUrl}/api/v1/b/basket"; _basketByPassUrl = $"{_settings.Value.PurchaseUrl}/api/v1/b/basket";
_purchaseUrl = $"{_settings.Value.PurchaseUrl}/api/v1"; _purchaseUrl = $"{_settings.Value.PurchaseUrl}/api/v1";
_httpContextAccesor = httpContextAccesor;
_apiClient = httpClient;
} }
public async Task<Basket> GetBasket(ApplicationUser user) public async Task<Basket> GetBasket(ApplicationUser user)
{ {
var token = await GetUserTokenAsync();
var getBasketUri = API.Basket.GetBasket(_basketByPassUrl, user.Id);
var uri = API.Basket.GetBasket(_basketByPassUrl, user.Id);
var dataString = await _apiClient.GetStringAsync(getBasketUri, token);
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) public async Task<Basket> UpdateBasket(Basket basket)
{ {
var token = await GetUserTokenAsync();
var updateBasketUri = API.Basket.UpdateBasket(_basketByPassUrl);
var uri = API.Basket.UpdateBasket(_basketByPassUrl);
var basketContent = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(updateBasketUri, basket, token);
var response = await _apiClient.PostAsync(uri, basketContent);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
@ -58,65 +54,64 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task Checkout(BasketDTO basket) public async Task Checkout(BasketDTO basket)
{ {
var token = await GetUserTokenAsync();
var updateBasketUri = API.Basket.CheckoutBasket(_basketByPassUrl);
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, basket, token);
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 token = await GetUserTokenAsync();
var updateBasketUri = API.Purchase.UpdateBasketItem(_purchaseUrl);
var userId = user.Id;
var response = await _apiClient.PutAsync(updateBasketUri, new
var basketUpdate = new
{ {
BasketId = userId,
BasketId = user.Id,
Updates = quantities.Select(kvp => new Updates = quantities.Select(kvp => new
{ {
BasketItemId = kvp.Key, BasketItemId = kvp.Key,
NewQty = kvp.Value NewQty = kvp.Value
}).ToArray() }).ToArray()
}, token);
};
var basketContent = new StringContent(JsonConvert.SerializeObject(basketUpdate), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PutAsync(uri, basketContent);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync(); var jsonResponse = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Basket>(jsonResponse); return JsonConvert.DeserializeObject<Basket>(jsonResponse);
} }
public async Task<Order> GetOrderDraft(string basketId) public async Task<Order> GetOrderDraft(string basketId)
{ {
var token = await GetUserTokenAsync();
var draftOrderUri = API.Purchase.GetOrderDraft(_purchaseUrl, basketId);
var json = await _apiClient.GetStringAsync(draftOrderUri, token);
return JsonConvert.DeserializeObject<Order>(json);
}
var uri = API.Purchase.GetOrderDraft(_purchaseUrl, basketId);
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 token = await GetUserTokenAsync();
var updateBasketUri = API.Purchase.AddItemToBasket(_purchaseUrl);
var userId = user.Id;
var uri = API.Purchase.AddItemToBasket(_purchaseUrl);
var response = await _apiClient.PostAsync(updateBasketUri, new
var newItem = new
{ {
CatalogItemId = productId, CatalogItemId = productId,
BasketId = userId,
BasketId = user.Id,
Quantity = 1 Quantity = 1
}, token);
};
}
var basketContent = new StringContent(JsonConvert.SerializeObject(newItem), System.Text.Encoding.UTF8, "application/json");
async Task<string> GetUserTokenAsync()
{
var context = _httpContextAccesor.HttpContext;
return await context.GetTokenAsync("access_token");
var response = await _apiClient.PostAsync(uri, basketContent);
} }
} }
} }

+ 12
- 32
src/Web/WebMVC/Services/CampaignService.cs 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 IHttpClient _apiClient;
private readonly IOptions<AppSettings> _settings;
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,
ILogger<CampaignService> logger, IHttpContextAccessor httpContextAccesor)
public CampaignService(IOptions<AppSettings> settings, HttpClient httpClient, ILogger<CampaignService> logger)
{ {
_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,
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; 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 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; 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");
}
} }
} }

+ 17
- 15
src/Web/WebMVC/Services/CatalogService.cs 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 IHttpClient _apiClient;
private readonly IOptions<AppSettings> _settings;
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;
} }
} }


+ 30
- 31
src/Web/WebMVC/Services/IdentityParser.cs View File

@ -8,37 +8,36 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Services namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
public class IdentityParser:IIdentityParser<ApplicationUser>
{
public ApplicationUser Parse(IPrincipal principal)
{
// Pattern matching 'is' expression
// assigns "claims" if "principal" is a "ClaimsPrincipal"
if (principal is ClaimsPrincipal claims)
{
return new ApplicationUser
{
CardHolderName = claims.Claims.FirstOrDefault(x => x.Type == "card_holder")?.Value ?? "",
CardNumber = claims.Claims.FirstOrDefault(x => x.Type == "card_number")?.Value ?? "",
Expiration = claims.Claims.FirstOrDefault(x => x.Type == "card_expiration")?.Value ?? "",
CardType = int.Parse(claims.Claims.FirstOrDefault(x => x.Type == "missing")?.Value ?? "0"),
City = claims.Claims.FirstOrDefault(x => x.Type == "address_city")?.Value ?? "",
Country = claims.Claims.FirstOrDefault(x => x.Type == "address_country")?.Value ?? "",
Email = claims.Claims.FirstOrDefault(x => x.Type == "email")?.Value ?? "",
Id = claims.Claims.FirstOrDefault(x => x.Type == "sub")?.Value ?? "",
LastName = claims.Claims.FirstOrDefault(x => x.Type == "last_name")?.Value ?? "",
Name = claims.Claims.FirstOrDefault(x => x.Type == "name")?.Value ?? "",
PhoneNumber = claims.Claims.FirstOrDefault(x => x.Type == "phone_number")?.Value ?? "",
SecurityNumber = claims.Claims.FirstOrDefault(x => x.Type == "card_security_number")?.Value ?? "",
State = claims.Claims.FirstOrDefault(x => x.Type == "address_state")?.Value ?? "",
Street = claims.Claims.FirstOrDefault(x => x.Type == "address_street")?.Value ?? "",
ZipCode = claims.Claims.FirstOrDefault(x => x.Type == "address_zip_code")?.Value ?? ""
};
}
throw new ArgumentException(message: "The principal must be a ClaimsPrincipal", paramName: nameof(principal));
}
}
public class IdentityParser : IIdentityParser<ApplicationUser>
{
public ApplicationUser Parse(IPrincipal principal)
{
// Pattern matching 'is' expression
// assigns "claims" if "principal" is a "ClaimsPrincipal"
if (principal is ClaimsPrincipal claims)
{
return new ApplicationUser
{
CardHolderName = claims.Claims.FirstOrDefault(x => x.Type == "card_holder")?.Value ?? "",
CardNumber = claims.Claims.FirstOrDefault(x => x.Type == "card_number")?.Value ?? "",
Expiration = claims.Claims.FirstOrDefault(x => x.Type == "card_expiration")?.Value ?? "",
CardType = int.Parse(claims.Claims.FirstOrDefault(x => x.Type == "missing")?.Value ?? "0"),
City = claims.Claims.FirstOrDefault(x => x.Type == "address_city")?.Value ?? "",
Country = claims.Claims.FirstOrDefault(x => x.Type == "address_country")?.Value ?? "",
Email = claims.Claims.FirstOrDefault(x => x.Type == "email")?.Value ?? "",
Id = claims.Claims.FirstOrDefault(x => x.Type == "sub")?.Value ?? "",
LastName = claims.Claims.FirstOrDefault(x => x.Type == "last_name")?.Value ?? "",
Name = claims.Claims.FirstOrDefault(x => x.Type == "name")?.Value ?? "",
PhoneNumber = claims.Claims.FirstOrDefault(x => x.Type == "phone_number")?.Value ?? "",
SecurityNumber = claims.Claims.FirstOrDefault(x => x.Type == "card_security_number")?.Value ?? "",
State = claims.Claims.FirstOrDefault(x => x.Type == "address_state")?.Value ?? "",
Street = claims.Claims.FirstOrDefault(x => x.Type == "address_street")?.Value ?? "",
ZipCode = claims.Claims.FirstOrDefault(x => x.Type == "address_zip_code")?.Value ?? ""
};
}
throw new ArgumentException(message: "The principal must be a ClaimsPrincipal", paramName: nameof(principal));
}
}
} }

+ 10
- 21
src/Web/WebMVC/Services/LocationService.cs 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.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 IHttpClient _apiClient;
private readonly IOptions<AppSettings> _settings;
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,
ILogger<CampaignService> logger, IHttpContextAccessor httpContextAccesor)
public LocationService(HttpClient httpClient, IOptions<AppSettings> settings, ILogger<CampaignService> logger)
{ {
_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 _apiClient.PostAsync(createOrUpdateUserLocationUri, location, authorizationToken);
var response = await _httpClient.PostAsync(uri, locationContent);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
}
private async Task<string> GetUserTokenAsync()
{
var context = _httpContextAccesor.HttpContext;
return await context.GetTokenAsync("access_token");
} }
} }
} }

+ 39
- 53
src/Web/WebMVC/Services/OrderingService.cs 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 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 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; _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 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; return response;
} }
async public Task<List<Order>> GetMyOrders(ApplicationUser user) 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 responseString = await _httpClient.GetStringAsync(uri);
var dataString = await _apiClient.GetStringAsync(allMyOrdersUri, token);
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 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) 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");
}
} }
} }

+ 23
- 22
src/Web/WebMVC/ViewModels/Annotations/CardExpiration.cs View File

@ -6,28 +6,29 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Annotations namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Annotations
{ {
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class CardExpirationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return false;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class CardExpirationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return false;
var monthString = value.ToString().Split('/')[0];
var yearString = $"20{value.ToString().Split('/')[1]}";
// Use the 'out' variable initializer to simplify
// the logic of validating the expiration date
if ((int.TryParse(monthString, out var month)) &&
(int.TryParse(yearString, out var year)))
{
DateTime d = new DateTime(year, month, 1);
var monthString = value.ToString().Split('/')[0];
var yearString = $"20{value.ToString().Split('/')[1]}";
// Use the 'out' variable initializer to simplify
// the logic of validating the expiration date
if ((int.TryParse(monthString, out var month)) &&
(int.TryParse(yearString, out var year)))
{
DateTime d = new DateTime(year, month, 1);
return d > DateTime.UtcNow;
} else
{
return false;
}
}
}
return d > DateTime.UtcNow;
}
else
{
return false;
}
}
}
} }

+ 15
- 15
src/Web/WebMVC/ViewModels/Annotations/LatitudeCoordinate.cs View File

@ -3,20 +3,20 @@ using System.ComponentModel.DataAnnotations;
namespace WebMVC.ViewModels.Annotations namespace WebMVC.ViewModels.Annotations
{ {
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class LatitudeCoordinate : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
double coordinate;
if (!double.TryParse(value.ToString(), out coordinate) || (coordinate < -90 || coordinate > 90))
{
return new ValidationResult
("Latitude must be between -90 and 90 degrees inclusive.");
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class LatitudeCoordinate : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
double coordinate;
if (!double.TryParse(value.ToString(), out coordinate) || (coordinate < -90 || coordinate > 90))
{
return new ValidationResult
("Latitude must be between -90 and 90 degrees inclusive.");
}
return ValidationResult.Success;
}
}
return ValidationResult.Success;
}
}
} }

+ 15
- 15
src/Web/WebMVC/ViewModels/Annotations/LongitudeCoordinate.cs View File

@ -3,20 +3,20 @@ using System.ComponentModel.DataAnnotations;
namespace WebMVC.ViewModels.Annotations namespace WebMVC.ViewModels.Annotations
{ {
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class LongitudeCoordinate : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
double coordinate;
if (!double.TryParse(value.ToString(), out coordinate) || (coordinate < -180 || coordinate > 180))
{
return new ValidationResult
("Longitude must be between -180 and 180 degrees inclusive.");
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class LongitudeCoordinate : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
double coordinate;
if (!double.TryParse(value.ToString(), out coordinate) || (coordinate < -180 || coordinate > 180))
{
return new ValidationResult
("Longitude must be between -180 and 180 degrees inclusive.");
}
return ValidationResult.Success;
}
}
return ValidationResult.Success;
}
}
} }

+ 25
- 25
src/Web/WebMVC/appsettings.json View File

@ -1,27 +1,27 @@
{ {
"CatalogUrl": "http://localhost:5101",
"OrderingUrl": "http://localhost:5102",
"BasketUrl": "http://localhost:5103",
"MarketingUrl": "http://localhost:5110",
"IdentityUrl": "http://localhost:5105",
"CallBackUrl": "http://localhost:5100/",
"LocationsUrl": "http://localhost:5109/",
"IsClusterEnv": "False",
"UseResilientHttp": "True",
"UseLoadTest": false,
"ActivateCampaignDetailFunction": "False",
"UseCustomizationData": false,
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}
},
"ApplicationInsights": {
"InstrumentationKey": ""
},
"HttpClientRetryCount": 8,
"HttpClientExceptionsAllowedBeforeBreaking": 7
"CatalogUrl": "http://localhost:5101",
"OrderingUrl": "http://localhost:5102",
"BasketUrl": "http://localhost:5103",
"MarketingUrl": "http://localhost:5110",
"IdentityUrl": "http://localhost:5105",
"CallBackUrl": "http://localhost:5100/",
"LocationsUrl": "http://localhost:5109/",
"IsClusterEnv": "False",
"UseResilientHttp": "True",
"UseLoadTest": false,
"ActivateCampaignDetailFunction": "False",
"UseCustomizationData": false,
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}
},
"ApplicationInsights": {
"InstrumentationKey": ""
},
"HttpClientRetryCount": 8,
"HttpClientExceptionsAllowedBeforeBreaking": 7
} }

+ 15
- 3
src/Web/WebSPA/Dockerfile View File

@ -1,12 +1,24 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS sdk-with-node
ENV NODE_VERSION 8.11.1
ENV NODE_DOWNLOAD_SHA 0e20787e2eda4cc31336d8327556ebc7417e8ee0a6ba0de96a09b0ec2b841f60
RUN curl -SL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" --output nodejs.tar.gz \
&& echo "$NODE_DOWNLOAD_SHA nodejs.tar.gz" | sha256sum -c - \
&& tar -xzf "nodejs.tar.gz" -C /usr/local --strip-components=1 \
&& rm nodejs.tar.gz \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
FROM sdk-with-node AS updated-npm
RUN npm i -g npm
FROM updated-npm as build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Web/WebSPA WORKDIR /src/src/Web/WebSPA
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 8
- 3
src/Web/WebSPA/Properties/launchSettings.json View File

@ -2,8 +2,12 @@
"iisSettings": { "iisSettings": {
"windowsAuthentication": false, "windowsAuthentication": false,
"anonymousAuthentication": true, "anonymousAuthentication": true,
"iis": {
"applicationUrl": "http://localhost/WebSPA",
"sslPort": 0
},
"iisExpress": { "iisExpress": {
"applicationUrl": "http://localhost:58018/",
"applicationUrl": "http://localhost:5104",
"sslPort": 0 "sslPort": 0
} }
}, },
@ -11,8 +15,9 @@
"IIS Express": { "IIS Express": {
"commandName": "IISExpress", "commandName": "IISExpress",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
"ASPNETCORE_ENVIRONMENT": "Development"
},
"use64Bit": true
} }
} }
} }

+ 1
- 1
src/Web/WebSPA/WebSPA.csproj View File

@ -90,7 +90,7 @@
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.0-beta8" /> <PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.0-beta8" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.1.1" /> <PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Redis" Version="0.4.1" /> <PackageReference Include="Microsoft.AspNetCore.DataProtection.Redis" Version="0.4.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup> </ItemGroup>


+ 3
- 3
src/Web/WebStatus/Dockerfile View File

@ -1,12 +1,12 @@
FROM microsoft/aspnetcore:2.0.5 AS base
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM microsoft/aspnetcore-build:2.0.5-2.1.4 AS build
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Web/WebStatus WORKDIR /src/src/Web/WebStatus
RUN dotnet restore -nowarn:msb3202,nu1503
RUN dotnet build --no-restore -c Release -o /app RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish FROM build AS publish


+ 1
- 0
src/Web/WebStatus/appsettings.json View File

@ -8,6 +8,7 @@
} }
}, },
"OrderingUrl": "http://localhost:5102/hc", "OrderingUrl": "http://localhost:5102/hc",
"OrderingBackgroundTasksUrl": "http://localhost:5111/hc",
"BasketUrl": "http://localhost:5103/hc", "BasketUrl": "http://localhost:5103/hc",
"CatalogUrl": "http://localhost:5101/hc", "CatalogUrl": "http://localhost:5101/hc",
"IdentityUrl": "http://localhost:5105/hc", "IdentityUrl": "http://localhost:5105/hc",


Loading…
Cancel
Save