Browse Source

Merge pull request #8 from dotnet-architecture/dev

upd fork
pull/1934/head
Taras Kovalenko 7 years ago
committed by GitHub
parent
commit
a7394be2bc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 607 additions and 647 deletions
  1. +1
    -1
      .env
  2. +15
    -15
      docker-compose.prod.yml
  3. BIN
      docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-v1.pdf
  4. BIN
      docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook).pdf
  5. BIN
      img/loadtests/k8ssettings.PNG
  6. BIN
      img/loadtests/loadtestproj_dir.PNG
  7. BIN
      img/loadtests/runloadtest.PNG
  8. BIN
      img/loadtests/sfmanifestsettings.PNG
  9. +3
    -2
      src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs
  10. +0
    -145
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/CommandBusRabbitMQ.cs
  11. +8
    -5
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  12. +1
    -0
      src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj
  13. +21
    -2
      src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs
  14. +110
    -119
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml
  15. +8
    -10
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Controls/AddBasketButton.xaml
  16. +1
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderService.cs
  17. +1
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/CheckoutViewModel.cs
  18. +4
    -5
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/CampaignDetailsView.xaml
  19. +4
    -5
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/CampaignView.xaml
  20. +4
    -5
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/CatalogView.xaml
  21. +4
    -5
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/CheckoutView.xaml
  22. +8
    -10
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/FiltersView.xaml
  23. +39
    -45
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/LoginView.xaml
  24. +23
    -29
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/MainView.xaml
  25. +4
    -5
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/OrderDetailView.xaml
  26. +46
    -44
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/SettingsView.xaml
  27. +5
    -6
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/BasketItemTemplate.xaml
  28. +9
    -11
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/CampaignTemplate.xaml
  29. +9
    -11
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/ProductTemplate.xaml
  30. +4
    -3
      src/Services/Basket/Basket.API/Startup.cs
  31. +2
    -2
      src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs
  32. +3
    -2
      src/Services/Catalog/Catalog.API/Startup.cs
  33. +1
    -1
      src/Services/Catalog/Catalog.API/appsettings.json
  34. +1
    -1
      src/Services/Identity/Identity.API/appsettings.json
  35. +3
    -2
      src/Services/Location/Locations.API/Startup.cs
  36. +1
    -1
      src/Services/Location/Locations.API/appsettings.json
  37. +5
    -4
      src/Services/Marketing/Marketing.API/Startup.cs
  38. +1
    -1
      src/Services/Marketing/Marketing.API/appsettings.json
  39. +15
    -12
      src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs
  40. +15
    -13
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
  41. +3
    -2
      src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs
  42. +15
    -12
      src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs
  43. +0
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs
  44. +3
    -3
      src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs
  45. +26
    -26
      src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs
  46. +41
    -0
      src/Services/Ordering/Ordering.API/Application/Queries/OrderViewModel.cs
  47. +2
    -2
      src/Services/Ordering/Ordering.API/Application/Validations/IdentifiedCommandValidator.cs
  48. +3
    -1
      src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
  49. +2
    -2
      src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs
  50. +4
    -3
      src/Services/Ordering/Ordering.API/Startup.cs
  51. +1
    -1
      src/Services/Ordering/Ordering.API/settings.json
  52. +2
    -6
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Address.cs
  53. +5
    -9
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  54. +4
    -5
      src/Services/Ordering/Ordering.Domain/Events/OrderStartedDomainEvent.cs
  55. +4
    -8
      src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs
  56. +0
    -4
      src/Services/Ordering/Ordering.Domain/SeedWork/ValueObject.cs
  57. +3
    -3
      src/Services/Ordering/Ordering.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs
  58. +1
    -10
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  59. +1
    -4
      src/Services/Ordering/Ordering.Infrastructure/Repositories/BuyerRepository.cs
  60. +4
    -3
      src/Services/Payment/Payment.API/Startup.cs
  61. +1
    -1
      src/Web/WebMVC/Views/Order/Index.cshtml
  62. +1
    -0
      src/Web/WebMVC/Views/Shared/_Layout.cshtml
  63. +1
    -1
      test/Services/IntegrationTests/Services/Basket/RedisBasketRepositoryTests.cs
  64. +2
    -2
      test/Services/IntegrationTests/Services/Catalog/CatalogScenarios.cs
  65. +3
    -3
      test/Services/LoadTest/LoadTest.csproj
  66. +4
    -1
      test/Services/LoadTest/OrderProducts.loadtest
  67. +1
    -1
      test/Services/LoadTest/WebMVC/CreateNewOrder.webtest
  68. +84
    -0
      test/Services/LoadTest/readme.md
  69. +4
    -4
      test/Services/UnitTest/Ordering/Application/IdentifiedCommandHandlerTest.cs
  70. +3
    -3
      test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs
  71. +0
    -3
      test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs

+ 1
- 1
.env View File

@ -5,7 +5,7 @@
# The IP below should be swapped to your real IP or DNS name, like 192.168.88.248, etc. if testing from remote browsers or mobile devices
ESHOP_EXTERNAL_DNS_NAME_OR_IP=localhost
ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP=10.121.122.92
ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP=10.121.122.162
#ESHOP_AZURE_REDIS_BASKET_DB=<YourAzureRedisBasketInfo>
#ESHOP_AZURE_STORAGE_CATALOG_URL=<YourAzureStorage_Catalog_BLOB_URL>


+ 15
- 15
docker-compose.prod.yml View File

@ -8,7 +8,7 @@ version: '3'
#
# IMPORTANT: Note that this compose file uses ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP instead of ESHOP_EXTERNAL_DNS_NAME_OR_IP
# Set ASPNETCORE_ENVIRONMENT=Development, instead Production, if you want to show up errors while testing.
# Set ASPNETCORE_ENVIRONMENT= Development or Production, depending if you want to show up errors while testing.
#
# You need to start it with the following CLI command:
# docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
@ -17,7 +17,7 @@ services:
basket.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${ESHOP_AZURE_REDIS_BASKET_DB:-basket.data}
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110.
@ -35,7 +35,7 @@ services:
catalog.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word}
- PicBaseUrl=${ESHOP_AZURE_STORAGE_CATALOG_URL:-http://localhost:5101/api/v1/catalog/items/[0]/pic/} #Local: You need to open your local dev-machine firewall at range 5100-5110.
@ -54,7 +54,7 @@ services:
identity.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- SpaClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5104
- XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback #localhost do not work for UWP login, so we have to use "external" IP always
@ -72,7 +72,7 @@ services:
ordering.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${ESHOP_AZURE_ORDERING_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word}
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110.
@ -92,7 +92,7 @@ services:
marketing.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${ESHOP_AZURE_MARKETING_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word}
- MongoConnectionString=${ESHOP_AZURE_COSMOSDB:-mongodb://nosql.data}
@ -116,7 +116,7 @@ services:
webspa:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- CatalogUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5101
- OrderingUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5102
@ -126,7 +126,7 @@ services:
- LocationsUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5109
- CatalogUrlHC=http://catalog.api/hc
- OrderingUrlHC=http://ordering.api/hc
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
- IdentityUrlHC=http://identity.api/hc
- BasketUrlHC=http://basket.api/hc
- MarketingUrlHC=http://marketing.api/hc
- PaymentUrlHC=http://payment.api/hc
@ -138,17 +138,17 @@ services:
webmvc:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- CatalogUrl=http://catalog.api
- OrderingUrl=http://ordering.api
- BasketUrl=http://basket.api
- LocationsUrl=http://locations.api
- IdentityUrl=http://10.0.75.1:5105
- MarketingUrl=http://marketing.api #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser. #Remote: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
- IdentityUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser. #Remote: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
- MarketingUrl=http://marketing.api
- CatalogUrlHC=http://catalog.api/hc
- OrderingUrlHC=http://ordering.api/hc
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
- IdentityUrlHC=http://identity.api/hc
- BasketUrlHC=http://basket.api/hc
- MarketingUrlHC=http://marketing.api/hc
- PaymentUrlHC=http://payment.api/hc
@ -161,7 +161,7 @@ services:
webstatus:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- CatalogUrl=http://catalog.api/hc
- OrderingUrl=http://ordering.api/hc
@ -179,7 +179,7 @@ services:
payment.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq}
- EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME}
@ -192,7 +192,7 @@ services:
locations.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${ESHOP_AZURE_COSMOSDB:-mongodb://nosql.data}
- Database=LocationsDb


BIN
docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook)-v1.pdf View File


BIN
docs/NET-Microservices-Architecture-for-Containerized-NET-Applications-(Microsoft-eBook).pdf View File


BIN
img/loadtests/k8ssettings.PNG View File

Before After
Width: 799  |  Height: 564  |  Size: 66 KiB

BIN
img/loadtests/loadtestproj_dir.PNG View File

Before After
Width: 352  |  Height: 293  |  Size: 9.0 KiB

BIN
img/loadtests/runloadtest.PNG View File

Before After
Width: 308  |  Height: 285  |  Size: 10 KiB

BIN
img/loadtests/sfmanifestsettings.PNG View File

Before After
Width: 607  |  Height: 544  |  Size: 66 KiB

+ 3
- 2
src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs View File

@ -5,9 +5,12 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{
public interface IEventBus
{
void Publish(IntegrationEvent @event);
void Subscribe<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>;
void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
@ -17,7 +20,5 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent;
void Publish(IntegrationEvent @event);
}
}

+ 0
- 145
src/BuildingBlocks/EventBus/EventBusRabbitMQ/CommandBusRabbitMQ.cs View File

@ -1,145 +0,0 @@
//using Microsoft.eShopOnContainers.BuildingBlocks.CommandBus;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Polly;
using Polly.Retry;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
/*
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public class CommandBusRabbitMQ : ICommandBus, IDisposable
{
const string BROKER_NAME = "eshop_command_bus";
private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<CommandBusRabbitMQ> _logger;
private IModel _consumerChannel;
private string _queueName;
private readonly Dictionary<string, IIntegrationCommandHandler> _handlers;
private readonly Dictionary<string, Type> _typeMappings;
public CommandBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection,
ILogger<CommandBusRabbitMQ> logger)
{
_logger = logger;
_persistentConnection = persistentConnection;
_handlers = new Dictionary<string, IIntegrationCommandHandler>();
_typeMappings = new Dictionary<string, Type>();
}
public void Send<T>(string name, T data)
{
Send(new IntegrationCommand<T>(name, data));
}
public void Handle<TC>(string name, IIntegrationCommandHandler<TC> handler)
{
_handlers.Add(name, handler);
_typeMappings.Add(name, typeof(TC));
}
public void Handle(string name, IIntegrationCommandHandler handler)
{
_handlers.Add(name, handler);
}
public void Handle<TI, TC>(TI handler) where TI : IIntegrationCommandHandler<TC>
{
var name = typeof(TI).Name;
_handlers.Add(name, handler);
_typeMappings.Add(name, typeof(TC));
}
private void Send<T>(IntegrationCommand<T> command)
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
var policy = RetryPolicy.Handle<BrokerUnreachableException>()
.Or<SocketException>()
.WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{
_logger.LogWarning(ex.ToString());
});
using (var channel = _persistentConnection.CreateModel())
{
var commandName = command.Name;
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
var message = JsonConvert.SerializeObject(command);
var body = Encoding.UTF8.GetBytes(message);
policy.Execute(() =>
{
channel.BasicPublish(exchange: BROKER_NAME,
routingKey: commandName,
basicProperties: null,
body: body);
});
}
}
private IModel CreateConsumerChannel()
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
var channel = _persistentConnection.CreateModel();
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
_queueName = channel.QueueDeclare().QueueName;
var consumer = new EventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
{
var commandName = ea.RoutingKey;
var message = Encoding.UTF8.GetString(ea.Body);
await InvokeHandler(commandName, message);
};
channel.BasicConsume(queue: _queueName,
noAck: true,
consumer: consumer);
channel.CallbackException += (sender, ea) =>
{
_consumerChannel.Dispose();
_consumerChannel = CreateConsumerChannel();
};
return channel;
}
private Task InvokeHandler(string commandName, string message)
{
if (_handlers.ContainsKey(commandName))
{
}
}
public void Dispose()
{
if (_consumerChannel != null)
{
_consumerChannel.Dispose();
}
}
}
}
*/

+ 8
- 5
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs View File

@ -32,11 +32,12 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private string _queueName;
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger,
ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager, int retryCount = 5)
ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager, string queueName = null, int retryCount = 5)
{
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_queueName = queueName;
_consumerChannel = CreateConsumerChannel();
_autofac = autofac;
_retryCount = retryCount;
@ -147,7 +148,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
_subsManager.RemoveDynamicSubscription<TH>(eventName);
}
public void Dispose()
{
if (_consumerChannel != null)
@ -170,7 +170,12 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
channel.ExchangeDeclare(exchange: BROKER_NAME,
type: "direct");
_queueName = channel.QueueDeclare().QueueName;
channel.QueueDeclare(queue: _queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
@ -196,8 +201,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
private async Task ProcessEvent(string eventName, string message)
{
if (_subsManager.HasSubscriptionsForEvent(eventName))
{
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))


+ 1
- 0
src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj View File

@ -6,6 +6,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Polly" Version="5.3.1" />
</ItemGroup>
</Project>

+ 21
- 2
src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs View File

@ -1,7 +1,10 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Polly;
using Polly.Retry;
using System;
using System.Data.SqlClient;
namespace Microsoft.AspNetCore.Hosting
{
@ -21,10 +24,26 @@ namespace Microsoft.AspNetCore.Hosting
{
logger.LogInformation($"Migrating database associated with context {typeof(TContext).Name}");
context.Database
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(new TimeSpan[]
{
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(15),
});
retry.Execute(() =>
{
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions.
context.Database
.Migrate();
seeder(context,services);
seeder(context, services);
});
logger.LogInformation($"Migrated database associated with context {typeof(TContext).Name}");
}


+ 110
- 119
src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml View File

@ -34,148 +34,139 @@
<Color x:Key="AndroidListViewBackgroundColor">Transparent</Color>
<Color x:Key="iOSListViewBackgroundColor">Transparent</Color>
<OnPlatform
x:TypeArguments="Color"
x:Key="ActivityIndicatorColor"
iOS="{ StaticResource iOSDefaultTintColor }" />
<OnPlatform x:TypeArguments="Color"
x:Key="ActivityIndicatorColor">
<On Platform="iOS" Value="{ StaticResource iOSDefaultTintColor }" />
</OnPlatform>
<OnPlatform
x:TypeArguments="Color"
x:Key="DefaultButtonClassBackgroundColor"
Android="{ StaticResource AndroidDefaultButtonClassBackgroundColor }"
iOS="{ StaticResource iOSDefaultButtonClassBackgroundColor }" />
<OnPlatform x:TypeArguments="Color"
x:Key="DefaultButtonClassBackgroundColor">
<On Platform="iOS" Value="{ StaticResource iOSDefaultButtonClassBackgroundColor }" />
<On Platform="Android" Value="{ StaticResource AndroidDefaultButtonClassBackgroundColor }" />
</OnPlatform>
<OnPlatform
x:TypeArguments="Color"
x:Key="DefaultButtonClassBorderColor"
Android="{ StaticResource AndroidDefaultButtonClassBorderColor }"
iOS="{ StaticResource iOSDefaultButtonClassBorderColor }" />
<OnPlatform x:TypeArguments="Color"
x:Key="DefaultButtonClassBorderColor">
<On Platform="iOS" Value="{ StaticResource iOSDefaultButtonClassBorderColor }" />
<On Platform="Android" Value="{ StaticResource AndroidDefaultButtonClassBorderColor }" />
</OnPlatform>
<OnPlatform
x:TypeArguments="Color"
x:Key="DefaultButtonClassTextColor"
Android="{ StaticResource AndroidDefaultButtonClassTextColor }"
iOS="{ StaticResource iOSDefaultButtonClassTextColor }" />
<OnPlatform x:TypeArguments="Color"
x:Key="DefaultButtonClassTextColor">
<On Platform="iOS" Value="{ StaticResource iOSDefaultButtonClassTextColor }" />
<On Platform="Android" Value="{ StaticResource AndroidDefaultButtonClassTextColor }" />
</OnPlatform>
<OnPlatform
x:TypeArguments="Color"
x:Key="EntryBackgroundColor"
Android="{ StaticResource AndroidEntryBackgroundColor }"
iOS="{ StaticResource iOSEntryBackgroundColor }" />
<OnPlatform x:TypeArguments="Color"
x:Key="EntryBackgroundColor">
<On Platform="iOS" Value="{ StaticResource iOSEntryBackgroundColor }" />
<On Platform="Android" Value="{ StaticResource AndroidEntryBackgroundColor }" />
</OnPlatform>
<OnPlatform
x:TypeArguments="Color"
x:Key="ThemeListViewBackgroundColor"
Android="{ StaticResource AndroidListViewBackgroundColor }"
iOS="{ StaticResource iOSListViewBackgroundColor }" />
<OnPlatform x:TypeArguments="Color"
x:Key="ThemeListViewBackgroundColor">
<On Platform="iOS" Value="{ StaticResource iOSListViewBackgroundColor }" />
<On Platform="Android" Value="{ StaticResource AndroidListViewBackgroundColor }" />
</OnPlatform>
<!-- SIZES -->
<OnPlatform
x:TypeArguments="x:Double"
x:Key="BaseButtonBorderRadius"
iOS="6" />
<OnPlatform x:TypeArguments="x:Double"
x:Key="BaseButtonBorderRadius">
<On Platform="iOS" Value="6" />
</OnPlatform>
<OnPlatform
x:TypeArguments="x:Double"
x:Key="BaseButtonBorderWidth"
Android="0"
iOS="0" />
<OnPlatform x:TypeArguments="x:Double"
x:Key="BaseButtonBorderWidth">
<On Platform="iOS, Android" Value="0" />
</OnPlatform>
<!-- FONTS -->
<OnPlatform
x:Key="MontserratRegular"
x:TypeArguments="x:String"
iOS="Montserrat-Regular"
Android="Montserrat-Regular.ttf#Montserrat"
WinPhone="Assets/Fonts/Montserrat-Regular.ttf#Montserrat"/>
<OnPlatform x:Key="MontserratRegular"
x:TypeArguments="x:String">
<On Platform="iOS" Value="Montserrat-Regular" />
<On Platform="Android" Value="Montserrat-Regular.ttf#Montserrat" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/Fonts/Montserrat-Regular.ttf#Montserrat" />
</OnPlatform>
<OnPlatform
x:Key="MontserratBold"
x:TypeArguments="x:String"
iOS="Montserrat-Bold"
Android="Montserrat-Bold.ttf#Montserrat"
WinPhone="Assets/Fonts/Montserrat-Bold.ttf#Montserrat"/>
<OnPlatform x:Key="MontserratBold"
x:TypeArguments="x:String">
<On Platform="iOS" Value="Montserrat-Bold" />
<On Platform="Android" Value="Montserrat-Bold.ttf#Montserrat" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/Fonts/Montserrat-Bold.ttf#Montserrat" />
</OnPlatform>
<OnPlatform
x:Key="SourceSansProRegular"
x:TypeArguments="x:String"
iOS="SourceSansPro-Regular"
Android="SourceSansPro-Regular.ttf#Source Sans Pro"
WinPhone="Assets/Fonts/SourceSansPro-Regular.ttf#Source Sans Pro"/>
<OnPlatform x:Key="SourceSansProRegular"
x:TypeArguments="x:String">
<On Platform="iOS" Value="SourceSansPro-Regular" />
<On Platform="Android" Value="SourceSansPro-Regular.ttf#Source Sans Pro" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/Fonts/SourceSansPro-Regular.ttf#Source Sans Pro" />
</OnPlatform>
<OnPlatform
x:TypeArguments="x:Double"
x:Key="BaseButtonFontSize"
Android="16"
iOS="18" />
<OnPlatform x:TypeArguments="x:Double"
x:Key="BaseButtonFontSize">
<On Platform="iOS" Value="18" />
<On Platform="Android" Value="16" />
</OnPlatform>
<OnPlatform
x:TypeArguments="x:Double"
x:Key="BaseFontSize"
Android="15"
iOS="16" />
<OnPlatform x:TypeArguments="x:Double"
x:Key="BaseFontSize">
<On Platform="iOS" Value="16" />
<On Platform="Android" Value="15" />
</OnPlatform>
<OnPlatform
x:Key="LittleSize"
x:TypeArguments="x:Double"
iOS="11"
Android="12"
WinPhone="12"/>
<OnPlatform x:Key="LittleSize"
x:TypeArguments="x:Double">
<On Platform="iOS" Value="11" />
<On Platform="Android, UWP, WinRT, WinPhone" Value="12" />
</OnPlatform>
<OnPlatform
x:Key="MidMediumSize"
x:TypeArguments="x:Double"
iOS="12"
Android="14"
WinPhone="14"/>
<OnPlatform x:Key="MidMediumSize"
x:TypeArguments="x:Double">
<On Platform="iOS" Value="12" />
<On Platform="Android, UWP, WinRT, WinPhone" Value="14" />
</OnPlatform>
<OnPlatform
x:Key="MediumSize"
x:TypeArguments="x:Double"
iOS="14"
Android="16"
WinPhone="16"/>
<OnPlatform x:Key="MediumSize"
x:TypeArguments="x:Double">
<On Platform="iOS" Value="14" />
<On Platform="Android, UWP, WinRT, WinPhone" Value="16" />
</OnPlatform>
<OnPlatform
x:Key="LargeSize"
x:TypeArguments="x:Double"
iOS="16"
Android="18"
WinPhone="18"/>
<OnPlatform x:Key="LargeSize"
x:TypeArguments="x:Double">
<On Platform="iOS" Value="16" />
<On Platform="Android, UWP, WinRT, WinPhone" Value="18" />
</OnPlatform>
<OnPlatform
x:Key="LargerSize"
x:TypeArguments="x:Double"
iOS="18"
Android="20"
WinPhone="20"/>
<OnPlatform x:Key="LargerSize"
x:TypeArguments="x:Double">
<On Platform="iOS" Value="18" />
<On Platform="Android, UWP, WinRT, WinPhone" Value="20" />
</OnPlatform>
<OnPlatform
x:Key="BigSize"
x:TypeArguments="x:Double"
iOS="20"
Android="24"
WinPhone="24"/>
<OnPlatform x:Key="BigSize"
x:TypeArguments="x:Double">
<On Platform="iOS" Value="20" />
<On Platform="Android, UWP, WinRT, WinPhone" Value="24" />
</OnPlatform>
<OnPlatform
x:Key="ExtraBigSize"
x:TypeArguments="x:Double"
iOS="24"
Android="32"
WinPhone="32"/>
<OnPlatform x:Key="ExtraBigSize"
x:TypeArguments="x:Double">
<On Platform="iOS" Value="24" />
<On Platform="Android, UWP, WinRT, WinPhone" Value="32" />
</OnPlatform>
<OnPlatform
x:Key="HugeSize"
x:TypeArguments="x:Double"
iOS="32"
Android="48"
WinPhone="48"/>
<OnPlatform x:Key="HugeSize"
x:TypeArguments="x:Double">
<On Platform="iOS" Value="32" />
<On Platform="Android, UWP, WinRT, WinPhone" Value="48" />
</OnPlatform>
<OnPlatform
x:TypeArguments="FontAttributes"
x:Key="BaseButtonFontAttributes"
Android="None"
iOS="Bold" />
<OnPlatform x:TypeArguments="FontAttributes"
x:Key="BaseButtonFontAttributes">
<On Platform="iOS" Value="Bold" />
<On Platform="Android" Value="None" />
</OnPlatform>
<!-- CONVERTERS -->
<converters:CountToBoolConverter x:Key="CountToBoolConverter" />


+ 8
- 10
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Controls/AddBasketButton.xaml View File

@ -44,11 +44,10 @@
<!-- ANDROID -->
<Grid>
<Grid.IsVisible>
<OnPlatform
x:TypeArguments="x:Boolean"
Android="True"
iOS="True"
WinPhone="False"/>
<OnPlatform x:TypeArguments="x:Boolean">
<On Platform="iOS, Android" Value="True" />
<On Platform="UWP, WinRT, WinPhone" Value="False" />
</OnPlatform>
</Grid.IsVisible>
<BoxView
BackgroundColor="{StaticResource LightGreenColor}"
@ -64,11 +63,10 @@
<!-- IOS & UWP -->
<Grid>
<Grid.IsVisible>
<OnPlatform
x:TypeArguments="x:Boolean"
Android="False"
iOS="False"
WinPhone="True"/>
<OnPlatform x:TypeArguments="x:Boolean">
<On Platform="iOS, Android" Value="False" />
<On Platform="UWP, WinRT, WinPhone" Value="True" />
</OnPlatform>
</Grid.IsVisible>
<Image
Source="Assets\circle_button_background.png"


+ 1
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderService.cs View File

@ -68,6 +68,7 @@ namespace eShopOnContainers.Core.Services.Order
CardSecurityNumber = order.CardSecurityNumber,
CardTypeId = order.CardTypeId,
City = order.ShippingCity,
State = order.ShippingState,
Country = order.ShippingCountry,
ZipCode = order.ShippingZipCode,
Street = order.ShippingStreet


+ 1
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/CheckoutViewModel.cs View File

@ -165,7 +165,7 @@ namespace eShopOnContainers.Core.ViewModels
await NavigationService.RemoveLastFromBackStackAsync();
// Show Dialog
await DialogService.ShowAlertAsync("Order sent successfully!", string.Format("Order {0}", Order.OrderNumber), "Ok");
await DialogService.ShowAlertAsync("Order sent successfully!", "Checkout", "Ok");
await NavigationService.RemoveLastFromBackStackAsync();
}
catch


+ 4
- 5
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/CampaignDetailsView.xaml View File

@ -171,11 +171,10 @@
VerticalOptions="Center"
HorizontalOptions="Center">
<ActivityIndicator.WidthRequest>
<OnPlatform
x:TypeArguments="x:Double"
iOS="100"
Android="100"
WinPhone="400" />
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS, Android" Value="100" />
<On Platform="UWP, WinRT, WinPhone" Value="400" />
</OnPlatform>
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
</Grid>

+ 4
- 5
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/CampaignView.xaml View File

@ -90,11 +90,10 @@
VerticalOptions="Center"
HorizontalOptions="Center">
<ActivityIndicator.WidthRequest>
<OnPlatform
x:TypeArguments="x:Double"
iOS="100"
Android="100"
WinPhone="400" />
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS, Android" Value="100" />
<On Platform="UWP, WinRT, WinPhone" Value="400" />
</OnPlatform>
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
</Grid>

+ 4
- 5
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/CatalogView.xaml View File

@ -114,11 +114,10 @@
VerticalOptions="Center"
HorizontalOptions="Center">
<ActivityIndicator.WidthRequest>
<OnPlatform
x:TypeArguments="x:Double"
iOS="100"
Android="100"
WinPhone="400" />
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS, Android" Value="100" />
<On Platform="UWP, WinRT, WinPhone" Value="400" />
</OnPlatform>
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
</Grid>

+ 4
- 5
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/CheckoutView.xaml View File

@ -231,11 +231,10 @@
VerticalOptions="Center"
HorizontalOptions="Center">
<ActivityIndicator.WidthRequest>
<OnPlatform
x:TypeArguments="x:Double"
iOS="100"
Android="100"
WinPhone="400" />
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS, Android" Value="100" />
<On Platform="UWP, WinRT, WinPhone" Value="400" />
</OnPlatform>
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
</Grid>

+ 8
- 10
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/FiltersView.xaml View File

@ -76,11 +76,10 @@
SelectedItem="{Binding Brand, Mode=TwoWay}"
Style="{StaticResource FilterPickerStyle}">
<Picker.HeightRequest>
<OnPlatform
x:TypeArguments="x:Double"
Android="48"
iOS="48"
WinPhone="36"/>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS, Android" Value="48" />
<On Platform="UWP, WinRT, WinPhone" Value="36" />
</OnPlatform>
</Picker.HeightRequest>
</Picker>
<!-- TYPE -->
@ -91,11 +90,10 @@
SelectedItem="{Binding Type, Mode=TwoWay}"
Style="{StaticResource FilterPickerStyle}">
<Picker.HeightRequest>
<OnPlatform
x:TypeArguments="x:Double"
Android="48"
iOS="48"
WinPhone="36"/>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS, Android" Value="48" />
<On Platform="UWP, WinRT, WinPhone" Value="36" />
</OnPlatform>
</Picker.HeightRequest>
</Picker>
<Button


+ 39
- 45
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/LoginView.xaml View File

@ -9,10 +9,9 @@
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
viewModelBase:ViewModelLocator.AutoWireViewModel="true">
<ContentPage.Title>
<OnPlatform
x:TypeArguments="x:String"
iOS="eShop on Containers"
WinPhone="eShop on Containers"/>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS, UWP, WinRT, WinPhone" Value="eShop on Containers" />
</OnPlatform>
</ContentPage.Title>
<ContentPage.Resources>
<ResourceDictionary>
@ -179,10 +178,10 @@
Style="{StaticResource HeaderLabelStyle}" />
<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
<Entry.Style>
<OnPlatform x:TypeArguments="Style"
iOS="{StaticResource EntryStyle}"
Android="{StaticResource EntryStyle}"
WinPhone="{StaticResource UwpEntryStyle}"/>
<OnPlatform x:TypeArguments="Style">
<On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
<On Platform="UWP, WinRT, WinPhone" Value="{StaticResource UwpEntryStyle}" />
</OnPlatform>
</Entry.Style>
<Entry.Behaviors>
<behaviors:EventToCommandBehavior
@ -208,10 +207,10 @@
IsPassword="True"
Text="{Binding Password.Value, Mode=TwoWay}">
<Entry.Style>
<OnPlatform x:TypeArguments="Style"
iOS="{StaticResource EntryStyle}"
Android="{StaticResource EntryStyle}"
WinPhone="{StaticResource UwpEntryStyle}"/>
<OnPlatform x:TypeArguments="Style">
<On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
<On Platform="UWP, WinRT, WinPhone" Value="{StaticResource UwpEntryStyle}" />
</OnPlatform>
</Entry.Style>
<Entry.Behaviors>
<behaviors:EventToCommandBehavior
@ -270,11 +269,10 @@
Grid.ColumnSpan="3"
Aspect="AspectFill">
<Image.Source>
<OnPlatform
x:TypeArguments="ImageSource"
Android="banner.png"
iOS="banner.png"
WinPhone="Assets\banner.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="banner.png" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets\banner.png" />
</OnPlatform>
</Image.Source>
</Image>
<Grid
@ -319,11 +317,10 @@
<Image
Style="{StaticResource SettingsImageStyle}">
<Image.Source>
<OnPlatform
x:TypeArguments="ImageSource"
WinPhone="Assets/app_settings.png"
Android="app_settings"
iOS="app_settings"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="app_settings" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/app_settings.png" />
</OnPlatform>
</Image.Source>
</Image>
<Grid.GestureRecognizers>
@ -345,24 +342,22 @@
AbsoluteLayout.LayoutFlags="All">
<WebView.Behaviors>
<OnPlatform x:TypeArguments="Behavior">
<OnPlatform.Android>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</OnPlatform.Android>
<OnPlatform.iOS>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</OnPlatform.iOS>
<OnPlatform.WinPhone>
<behaviors:EventToCommandBehavior
EventName="Navigated"
EventArgsConverter="{StaticResource WebNavigatedEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</OnPlatform.WinPhone>
<On Platform="iOS, Android">
<On.Value>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</On.Value>
</On>
<On Platform="UWP">
<On.Value>
<behaviors:EventToCommandBehavior
EventName="Navigated"
EventArgsConverter="{StaticResource WebNavigatedEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</On.Value>
</On>
</OnPlatform>
</WebView.Behaviors>
</WebView>
@ -376,11 +371,10 @@
VerticalOptions="Center"
HorizontalOptions="Center">
<ActivityIndicator.WidthRequest>
<OnPlatform
x:TypeArguments="x:Double"
iOS="100"
Android="100"
WinPhone="400" />
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS, Android" Value="100" />
<On Platform="UWP, WinRT, WinPhone" Value="400" />
</OnPlatform>
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
</Grid>

+ 23
- 29
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/MainView.xaml View File

@ -10,21 +10,19 @@
BarTextColor="{StaticResource WhiteColor}"
viewModelBase:ViewModelLocator.AutoWireViewModel="true">
<TabbedPage.Title>
<OnPlatform
x:TypeArguments="x:String"
iOS="eShop on Containers"
WinPhone="eShop on Containers"/>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS, UWP, WinRT, WinPhone" Value="eShop on Containers" />
</OnPlatform>
</TabbedPage.Title>
<ContentPage.ToolbarItems>
<ToolbarItem
Command="{Binding SettingsCommand}"
Text="Settings">
<ToolbarItem.Icon>
<OnPlatform
x:TypeArguments="FileImageSource"
WinPhone="Assets/app_settings.png"
Android="app_settings"
iOS="app_settings"/>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS, Android" Value="app_settings" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/app_settings.png" />
</OnPlatform>
</ToolbarItem.Icon>
</ToolbarItem>
</ContentPage.ToolbarItems>
@ -32,22 +30,20 @@
<views:CatalogView
x:Name="HomeView">
<views:CatalogView.Icon>
<OnPlatform
x:TypeArguments="FileImageSource"
Android="menu_filter"
iOS="menu_filter"
WinPhone="Assets\menu_filter.png"/>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS, Android" Value="menu_filter" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets\menu_filter.png" />
</OnPlatform>
</views:CatalogView.Icon>
</views:CatalogView>
<!-- PROFILE -->
<views:ProfileView
x:Name="ProfileView">
<views:ProfileView.Icon>
<OnPlatform
x:TypeArguments="FileImageSource"
Android="menu_profile"
iOS="menu_profile"
WinPhone="Assets\menu_profile.png"/>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS, Android" Value="menu_profile" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets\menu_profile.png" />
</OnPlatform>
</views:ProfileView.Icon>
</views:ProfileView>
<!-- BASKET -->
@ -56,22 +52,20 @@
controls:CustomTabbedPage.BadgeText="{Binding BadgeCount}"
controls:CustomTabbedPage.BadgeColor="{StaticResource LightGreenColor}">
<views:BasketView.Icon>
<OnPlatform
x:TypeArguments="FileImageSource"
Android="menu_cart"
iOS="menu_cart"
WinPhone="Assets\menu_cart.png"/>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS, Android" Value="menu_cart" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets\menu_cart.png" />
</OnPlatform>
</views:BasketView.Icon>
</views:BasketView>
<!-- CAMPAIGNS -->
<views:CampaignView
x:Name="CampaignView">
<views:CampaignView.Icon>
<OnPlatform
x:TypeArguments="FileImageSource"
Android="menu_campaigns"
iOS="menu_campaigns"
WinPhone="Assets\menu_campaigns.png"/>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS, Android" Value="menu_campaigns" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets\menu_campaigns.png" />
</OnPlatform>
</views:CampaignView.Icon>
</views:CampaignView>
</TabbedPage>

+ 4
- 5
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/OrderDetailView.xaml View File

@ -264,11 +264,10 @@
VerticalOptions="Center"
HorizontalOptions="Center">
<ActivityIndicator.WidthRequest>
<OnPlatform
x:TypeArguments="x:Double"
iOS="100"
Android="100"
WinPhone="400" />
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS, Android" Value="100" />
<On Platform="UWP, WinRT, WinPhone" Value="400" />
</OnPlatform>
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
</Grid>


+ 46
- 44
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/SettingsView.xaml View File

@ -99,11 +99,10 @@
<animations:FadeInAnimation
Direction="Up">
<animations:FadeInAnimation.Duration>
<OnPlatform
x:TypeArguments="x:String"
Android="500"
iOS="0"
WinPhone="500"/>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS" Value="0" />
<On Platform="Android, UWP, WinRT, WinPhone" Value="500" />
</OnPlatform>
</animations:FadeInAnimation.Duration>
</animations:FadeInAnimation>
</animations:StoryBoard>
@ -156,16 +155,18 @@
Command="{Binding ToggleMockServicesCommand}"
Style="{StaticResource SettingsToggleButtonStyle}">
<controls:ToggleButton.CheckedImage>
<OnPlatform x:TypeArguments="ImageSource"
Android="switch_on.png"
iOS="switchOn.png"
WinPhone="Assets/switchOn.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS" Value="switchOn.png" />
<On Platform="Android" Value="switch_on.png" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/switchOn.png" />
</OnPlatform>
</controls:ToggleButton.CheckedImage>
<controls:ToggleButton.UnCheckedImage>
<OnPlatform x:TypeArguments="ImageSource"
Android="switch_off.png"
iOS="switchOff.png"
WinPhone="Assets/switchOff.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS" Value="switchOff.png" />
<On Platform="Android" Value="switch_off.png" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/switchOff.png" />
</OnPlatform>
</controls:ToggleButton.UnCheckedImage>
</controls:ToggleButton>
<!-- ENDPOINT -->
@ -181,11 +182,10 @@
<Entry
Text="{Binding Endpoint, Mode=TwoWay}">
<Entry.Style>
<OnPlatform
x:TypeArguments="Style"
iOS="{StaticResource SettingsEntryStyle}"
Android="{StaticResource SettingsEntryStyle}"
WinPhone="{StaticResource SettingsUwpEntryStyle}"/>
<OnPlatform x:TypeArguments="Style">
<On Platform="iOS, Android" Value="{StaticResource SettingsEntryStyle}" />
<On Platform="UWP, WinRT, WinPhone" Value="{StaticResource SettingsUwpEntryStyle}" />
</OnPlatform>
</Entry.Style>
</Entry>
</StackLayout>
@ -212,16 +212,18 @@
Style="{StaticResource SettingsToggleButtonStyle}"
IsVisible="{Binding UserIsLogged}">
<controls:ToggleButton.CheckedImage>
<OnPlatform x:TypeArguments="ImageSource"
Android="switch_on.png"
iOS="switchOn.png"
WinPhone="Assets/switchOn.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS" Value="switchOn.png" />
<On Platform="Android" Value="switch_on.png" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/switchOn.png" />
</OnPlatform>
</controls:ToggleButton.CheckedImage>
<controls:ToggleButton.UnCheckedImage>
<OnPlatform x:TypeArguments="ImageSource"
Android="switch_off.png"
iOS="switchOff.png"
WinPhone="Assets/switchOff.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS" Value="switchOff.png" />
<On Platform="Android" Value="switch_off.png" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/switchOff.png" />
</OnPlatform>
</controls:ToggleButton.UnCheckedImage>
</controls:ToggleButton>
<!-- FAKE LOCATIONS -->
@ -238,11 +240,10 @@
Text="{Binding Latitude, Mode=TwoWay, Converter={StaticResource DoubleConverter}}"
Keyboard="Text">
<Entry.Style>
<OnPlatform
x:TypeArguments="Style"
iOS="{StaticResource SettingsEntryStyle}"
Android="{StaticResource SettingsEntryStyle}"
WinPhone="{StaticResource SettingsUwpEntryStyle}"/>
<OnPlatform x:TypeArguments="Style">
<On Platform="iOS, Android" Value="{StaticResource SettingsEntryStyle}" />
<On Platform="UWP, WinRT, WinPhone" Value="{StaticResource SettingsUwpEntryStyle}" />
</OnPlatform>
</Entry.Style>
</Entry>
<Label
@ -252,11 +253,10 @@
Text="{Binding Longitude, Mode=TwoWay, Converter={StaticResource DoubleConverter}}"
Keyboard="Text">
<Entry.Style>
<OnPlatform
x:TypeArguments="Style"
iOS="{StaticResource SettingsEntryStyle}"
Android="{StaticResource SettingsEntryStyle}"
WinPhone="{StaticResource SettingsUwpEntryStyle}"/>
<OnPlatform x:TypeArguments="Style">
<On Platform="iOS, Android" Value="{StaticResource SettingsEntryStyle}" />
<On Platform="UWP, WinRT, WinPhone" Value="{StaticResource SettingsUwpEntryStyle}" />
</OnPlatform>
</Entry.Style>
</Entry>
<Button
@ -289,16 +289,18 @@
Style="{StaticResource SettingsToggleButtonStyle}"
IsVisible="{Binding UseFakeLocation, Converter={StaticResource InverseBoolConverter}}">
<controls:ToggleButton.CheckedImage>
<OnPlatform x:TypeArguments="ImageSource"
Android="switch_on.png"
iOS="switchOn.png"
WinPhone="Assets/switchOn.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS" Value="switchOn.png" />
<On Platform="Android" Value="switch_on.png" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/switchOn.png" />
</OnPlatform>
</controls:ToggleButton.CheckedImage>
<controls:ToggleButton.UnCheckedImage>
<OnPlatform x:TypeArguments="ImageSource"
Android="switch_off.png"
iOS="switchOff.png"
WinPhone="Assets/switchOff.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS" Value="switchOff.png" />
<On Platform="Android" Value="switch_off.png" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/switchOff.png" />
</OnPlatform>
</controls:ToggleButton.UnCheckedImage>
</controls:ToggleButton>
</Grid>


+ 5
- 6
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/BasketItemTemplate.xaml View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
@ -71,11 +71,10 @@
<Grid
BackgroundColor="{StaticResource BackgroundColor}">
<Grid.Padding>
<OnPlatform
x:TypeArguments="Thickness"
Android="0"
iOS="0"
WinPhone="12, 0"/>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS, Android" Value="0" />
<On Platform="UWP, WinRT, WinPhone" Value="12, 0" />
</OnPlatform>
</Grid.Padding>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />


+ 9
- 11
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/CampaignTemplate.xaml View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
@ -59,18 +59,16 @@
Source="{Binding PictureUri}"
Aspect="AspectFill">
<ffimageloading:CachedImage.LoadingPlaceholder>
<OnPlatform
x:TypeArguments="ImageSource"
iOS="default_campaign"
Android="default_campaign"
WinPhone="Assets/default_campaign.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="default_campaign" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/default_campaign.png" />
</OnPlatform>
</ffimageloading:CachedImage.LoadingPlaceholder>
<ffimageloading:CachedImage.ErrorPlaceholder>
<OnPlatform
x:TypeArguments="ImageSource"
iOS="noimage"
Android="noimage"
WinPhone="Assets/noimage.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="noimage" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/noimage.png" />
</OnPlatform>
</ffimageloading:CachedImage.ErrorPlaceholder>
</ffimageloading:CachedImage>
<!-- NAME -->


+ 9
- 11
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/ProductTemplate.xaml View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
@ -76,18 +76,16 @@
Source="{Binding PictureUri}"
Aspect="AspectFill">
<ffimageloading:CachedImage.LoadingPlaceholder>
<OnPlatform
x:TypeArguments="ImageSource"
iOS="default_product"
Android="default_product"
WinPhone="Assets/default_product.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="default_product" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/default_product.png" />
</OnPlatform>
</ffimageloading:CachedImage.LoadingPlaceholder>
<ffimageloading:CachedImage.ErrorPlaceholder>
<OnPlatform
x:TypeArguments="ImageSource"
iOS="noimage"
Android="noimage"
WinPhone="Assets/noimage.png"/>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="noimage" />
<On Platform="UWP, WinRT, WinPhone" Value="Assets/noimage.png" />
</OnPlatform>
</ffimageloading:CachedImage.ErrorPlaceholder>
</ffimageloading:CachedImage>
<Grid


+ 4
- 3
src/Services/Basket/Basket.API/Startup.cs View File

@ -257,6 +257,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
private void RegisterEventBus(IServiceCollection services)
{
var subscriptionClientName = Configuration["SubscriptionClientName"];
if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
@ -264,8 +266,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var subscriptionClientName = Configuration["SubscriptionClientName"];
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope);
@ -286,7 +287,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, retryCount);
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}


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

@ -180,7 +180,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
catalogItem = productToUpdate;
_catalogContext.CatalogItems.Update(catalogItem);
if (raiseProductPriceChangedEvent) // Save and publish integration event if price has changed
if (raiseProductPriceChangedEvent) // Save product's data and publish integration event through the Event Bus if price has changed
{
//Create Integration Event to be published through the Event Bus
var priceChangedEvent = new ProductPriceChangedIntegrationEvent(catalogItem.Id, productToUpdate.Price, oldPrice);
@ -191,7 +191,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
// Publish through the Event Bus and mark the saved event as published
await _catalogIntegrationEventService.PublishThroughEventBusAsync(priceChangedEvent);
}
else // Save updated product
else // Just save the updated product because the Product's Price hasn't changed.
{
await _catalogContext.SaveChangesAsync();
}


+ 3
- 2
src/Services/Catalog/Catalog.API/Startup.cs View File

@ -224,6 +224,8 @@
private void RegisterEventBus(IServiceCollection services)
{
var subscriptionClientName = Configuration["SubscriptionClientName"];
if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
@ -232,7 +234,6 @@
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var subscriptionClientName = Configuration["SubscriptionClientName"];
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope);
@ -254,7 +255,7 @@
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, retryCount);
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}


+ 1
- 1
src/Services/Catalog/Catalog.API/appsettings.json View File

@ -5,7 +5,7 @@
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}


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

@ -8,7 +8,7 @@
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}


+ 3
- 2
src/Services/Location/Locations.API/Startup.cs View File

@ -227,6 +227,8 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
private void RegisterEventBus(IServiceCollection services)
{
var subscriptionClientName = Configuration["SubscriptionClientName"];
if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
@ -235,7 +237,6 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var subscriptionClientName = Configuration["SubscriptionClientName"];
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope);
@ -256,7 +257,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, retryCount);
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}


+ 1
- 1
src/Services/Location/Locations.API/appsettings.json View File

@ -5,7 +5,7 @@
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}


+ 5
- 4
src/Services/Marketing/Marketing.API/Startup.cs View File

@ -246,8 +246,10 @@
});
}
private void RegisterEventBus(IServiceCollection services)
private void RegisterEventBus(IServiceCollection services)
{
var subscriptionClientName = Configuration["SubscriptionClientName"];
if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
@ -255,8 +257,7 @@
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var subscriptionClientName = Configuration["SubscriptionClientName"];
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope);
@ -277,7 +278,7 @@
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, retryCount);
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}


+ 1
- 1
src/Services/Marketing/Marketing.API/appsettings.json View File

@ -2,7 +2,7 @@
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
"Default": "Trace"
}
},
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word",


+ 15
- 12
src/Services/Ordering/Ordering.API/Application/Commands/CancelOrderCommandHandler.cs View File

@ -9,18 +9,7 @@ using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class CancelOrderCommandIdentifiedHandler : IdentifierCommandHandler<CancelOrderCommand, bool>
{
public CancelOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
// Regular CommandHandler
public class CancelOrderCommandHandler : IAsyncRequestHandler<CancelOrderCommand, bool>
{
private readonly IOrderRepository _orderRepository;
@ -43,4 +32,18 @@ namespace Ordering.API.Application.Commands
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
// Use for Idempotency in Command process
public class CancelOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CancelOrderCommand, bool>
{
public CancelOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
}

+ 15
- 13
src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs View File

@ -7,19 +7,7 @@
using System;
using System.Threading.Tasks;
public class CreateOrderCommandIdentifiedHandler : IdentifierCommandHandler<CreateOrderCommand, bool>
{
public CreateOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for creating order.
}
}
// Regular CommandHandler
public class CreateOrderCommandHandler
: IAsyncRequestHandler<CreateOrderCommand, bool>
{
@ -55,4 +43,18 @@
.SaveEntitiesAsync();
}
}
// Use for Idempotency in Command process
public class CreateOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CreateOrderCommand, bool>
{
public CreateOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for creating order.
}
}
}

src/Services/Ordering/Ordering.API/Application/Commands/IdentifierCommandHandler.cs → src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommandHandler.cs View File

@ -10,13 +10,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
/// </summary>
/// <typeparam name="T">Type of the command handler that performs the operation if request is not duplicated</typeparam>
/// <typeparam name="R">Return value of the inner command handler</typeparam>
public class IdentifierCommandHandler<T, R> : IAsyncRequestHandler<IdentifiedCommand<T, R>, R>
public class IdentifiedCommandHandler<T, R> : IAsyncRequestHandler<IdentifiedCommand<T, R>, R>
where T : IRequest<R>
{
private readonly IMediator _mediator;
private readonly IRequestManager _requestManager;
public IdentifierCommandHandler(IMediator mediator, IRequestManager requestManager)
public IdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager)
{
_mediator = mediator;
_requestManager = requestManager;
@ -48,6 +48,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
{
await _requestManager.CreateRequestForCommandAsync<T>(message.Id);
// Send the embeded business command to mediator so it runs its related CommandHandler
var result = await _mediator.Send(message.Command);
return result;

+ 15
- 12
src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs View File

@ -6,18 +6,7 @@ using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class ShipOrderCommandIdentifiedHandler : IdentifierCommandHandler<ShipOrderCommand, bool>
{
public ShipOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
// Regular CommandHandler
public class ShipOrderCommandHandler : IAsyncRequestHandler<ShipOrderCommand, bool>
{
private readonly IOrderRepository _orderRepository;
@ -40,4 +29,18 @@ namespace Ordering.API.Application.Commands
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
// Use for Idempotency in Command process
public class ShipOrderIdentifiedCommandHandler : IdentifiedCommandHandler<ShipOrderCommand, bool>
{
public ShipOrderIdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
}

+ 0
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs View File

@ -25,7 +25,6 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent
public async Task Handle(OrderStartedDomainEvent orderStartedEvent)
{
var cardTypeId = (orderStartedEvent.CardTypeId != 0) ? orderStartedEvent.CardTypeId : 1;
var buyer = await _buyerRepository.FindAsync(orderStartedEvent.UserId);
bool buyerOriginallyExisted = (buyer == null) ? false : true;


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

@ -5,10 +5,10 @@
public interface IOrderQueries
{
Task<dynamic> GetOrderAsync(int id);
Task<Order> GetOrderAsync(int id);
Task<IEnumerable<dynamic>> GetOrdersAsync();
Task<IEnumerable<OrderSummary>> GetOrdersAsync();
Task<IEnumerable<dynamic>> GetCardTypesAsync();
Task<IEnumerable<CardType>> GetCardTypesAsync();
}
}

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

@ -1,11 +1,9 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries
{
using Dapper;
using Microsoft.Extensions.Configuration;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System;
using System.Dynamic;
using System.Collections.Generic;
public class OrderQueries
@ -19,7 +17,7 @@
}
public async Task<dynamic> GetOrderAsync(int id)
public async Task<Order> GetOrderAsync(int id)
{
using (var connection = new SqlConnection(_connectionString))
{
@ -44,13 +42,13 @@
}
}
public async Task<IEnumerable<dynamic>> GetOrdersAsync()
public async Task<IEnumerable<OrderSummary>> GetOrdersAsync()
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
return await connection.QueryAsync<dynamic>(@"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
LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid
LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id
@ -59,39 +57,41 @@
}
}
public async Task<IEnumerable<dynamic>> GetCardTypesAsync()
public async Task<IEnumerable<CardType>> GetCardTypesAsync()
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
return await connection.QueryAsync<dynamic>("SELECT * FROM ordering.cardtypes");
return await connection.QueryAsync<CardType>("SELECT * FROM ordering.cardtypes");
}
}
private dynamic MapOrderItems(dynamic result)
private Order MapOrderItems(dynamic result)
{
dynamic order = new ExpandoObject();
order.ordernumber = result[0].ordernumber;
order.date = result[0].date;
order.status = result[0].status;
order.description = result[0].description;
order.street = result[0].street;
order.city = result[0].city;
order.zipcode = result[0].zipcode;
order.country = result[0].country;
order.orderitems = new List<dynamic>();
order.total = 0;
var order = new Order
{
ordernumber = result[0].ordernumber,
date = result[0].date,
status = result[0].status,
description = result[0].description,
street = result[0].street,
city = result[0].city,
zipcode = result[0].zipcode,
country = result[0].country,
orderitems = new List<Orderitem>(),
total = 0
};
foreach (dynamic item in result)
{
dynamic orderitem = new ExpandoObject();
orderitem.productname = item.productname;
orderitem.units = item.units;
orderitem.unitprice = item.unitprice;
orderitem.pictureurl = item.pictureurl;
var orderitem = new Orderitem
{
productname = item.productname,
units = item.units,
unitprice = (double)item.unitprice,
pictureurl = item.pictureurl
};
order.total += item.units * item.unitprice;
order.orderitems.Add(orderitem);


+ 41
- 0
src/Services/Ordering/Ordering.API/Application/Queries/OrderViewModel.cs View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries
{
public class Orderitem
{
public string productname { get; set; }
public int units { get; set; }
public double unitprice { get; set; }
public string pictureurl { get; set; }
}
public class Order
{
public int ordernumber { get; set; }
public DateTime date { get; set; }
public string status { get; set; }
public string description { get; set; }
public string street { get; set; }
public string city { get; set; }
public string zipcode { get; set; }
public string country { get; set; }
public List<Orderitem> orderitems { get; set; }
public decimal total { get; set; }
}
public class OrderSummary
{
public int ordernumber { get; set; }
public DateTime date { get; set; }
public string status { get; set; }
public double total { get; set; }
}
public class CardType
{
public int Id { get; set; }
public string Name { get; set; }
}
}

src/Services/Ordering/Ordering.API/Application/Validations/IdentifierCommandValidator.cs → src/Services/Ordering/Ordering.API/Application/Validations/IdentifiedCommandValidator.cs View File

@ -3,9 +3,9 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
namespace Ordering.API.Application.Validations
{
public class IdentifierCommandValidator : AbstractValidator<IdentifiedCommand<CreateOrderCommand,bool>>
public class IdentifiedCommandValidator : AbstractValidator<IdentifiedCommand<CreateOrderCommand,bool>>
{
public IdentifierCommandValidator()
public IdentifiedCommandValidator()
{
RuleFor(command => command.Id).NotEmpty();
}

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

@ -64,7 +64,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[Route("{orderId:int}")]
[HttpGet]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(Order),(int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetOrder(int orderId)
{
@ -83,6 +83,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[Route("")]
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<OrderSummary>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetOrders()
{
var orderTask = _orderQueries.GetOrdersAsync();
@ -94,6 +95,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[Route("cardtypes")]
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<CardType>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetCardTypes()
{
var cardTypes = await _orderQueries


+ 2
- 2
src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs View File

@ -23,11 +23,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
builder.RegisterAssemblyTypes(typeof(CreateOrderCommand).GetTypeInfo().Assembly)
.AsClosedTypesOf(typeof(IAsyncRequestHandler<,>));
// Register all the event classes (they implement IAsyncNotificationHandler) in assembly holding the Commands
// Register the DomainEventHandler classes (they implement IAsyncNotificationHandler<>) in assembly holding the Domain Events
builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly)
.AsClosedTypesOf(typeof(IAsyncNotificationHandler<>));
// Register the Command's Validators (Validators based on FluentValidation library)
builder
.RegisterAssemblyTypes(typeof(CreateOrderCommandValidator).GetTypeInfo().Assembly)
.Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))


+ 4
- 3
src/Services/Ordering/Ordering.API/Startup.cs View File

@ -294,6 +294,8 @@
private void RegisterEventBus(IServiceCollection services)
{
var subscriptionClientName = Configuration["SubscriptionClientName"];
if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
@ -301,8 +303,7 @@
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var subscriptionClientName = Configuration["SubscriptionClientName"];
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope);
@ -323,7 +324,7 @@
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, retryCount);
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}


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

@ -5,7 +5,7 @@
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}


+ 2
- 6
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Address.cs View File

@ -4,17 +4,12 @@ using System.Collections.Generic;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
{
public class Address
:ValueObject
public class Address : ValueObject
{
public String Street { get; private set; }
public String City { get; private set; }
public String State { get; private set; }
public String Country { get; private set; }
public String ZipCode { get; private set; }
private Address() { }
@ -30,6 +25,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
protected override IEnumerable<object> GetAtomicValues()
{
// Using a yield return statement to return each element one at a time
yield return Street;
yield return City;
yield return State;


+ 5
- 9
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

@ -15,6 +15,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
private DateTime _orderDate;
// Address is a Value Object pattern example persisted as EF Core 2.0 owned entity
public Address Address { get; private set; }
private int? _buyerId;
@ -29,12 +30,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
// but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.
private readonly List<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
// Using List<>.AsReadOnly()
// This will create a read only wrapper around the private list so is protected against "external updates".
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
private int? _paymentMethodId;
@ -177,16 +173,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
private void AddOrderStartedDomainEvent(string userId, int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName, DateTime cardExpiration)
{
var orderStartedDomainEvent = new OrderStartedDomainEvent(
this, userId, cardTypeId, cardNumber, cardSecurityNumber,
cardHolderName, cardExpiration);
var orderStartedDomainEvent = new OrderStartedDomainEvent(this, userId, cardTypeId,
cardNumber, cardSecurityNumber,
cardHolderName, cardExpiration);
this.AddDomainEvent(orderStartedDomainEvent);
}
private void StatusChangeException(OrderStatus orderStatusToChange)
{
throw new OrderingDomainException($"Not possible to change order status from {OrderStatus.Name} to {orderStatusToChange.Name}.");
throw new OrderingDomainException($"Is not possible to change the order status from {OrderStatus.Name} to {orderStatusToChange.Name}.");
}
public decimal GetTotal()


+ 4
- 5
src/Services/Ordering/Ordering.Domain/Events/OrderStartedDomainEvent.cs View File

@ -9,8 +9,7 @@ namespace Ordering.Domain.Events
/// <summary>
/// Event used when an order is created
/// </summary>
public class OrderStartedDomainEvent
: INotification
public class OrderStartedDomainEvent : INotification
{
public string UserId { get; private set; }
public int CardTypeId { get; private set; }
@ -21,9 +20,9 @@ namespace Ordering.Domain.Events
public Order Order { get; private set; }
public OrderStartedDomainEvent(Order order, string userId,
int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName,
DateTime cardExpiration)
int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName,
DateTime cardExpiration)
{
Order = order;
UserId = userId;


+ 4
- 8
src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs View File

@ -6,12 +6,8 @@
public abstract class Entity
{
int? _requestedHashCode;
int _Id;
private List<INotification> _domainEvents;
int _Id;
public virtual int Id
{
get
@ -24,13 +20,14 @@
}
}
public List<INotification> DomainEvents => _domainEvents;
private List<INotification> _domainEvents;
public List<INotification> DomainEvents => _domainEvents;
public void AddDomainEvent(INotification eventItem)
{
_domainEvents = _domainEvents ?? new List<INotification>();
_domainEvents.Add(eventItem);
}
public void RemoveDomainEvent(INotification eventItem)
{
if (_domainEvents is null) return;
@ -74,7 +71,6 @@
return base.GetHashCode();
}
public static bool operator ==(Entity left, Entity right)
{
if (Object.Equals(left, null))


+ 0
- 4
src/Services/Ordering/Ordering.Domain/SeedWork/ValueObject.cs View File

@ -14,16 +14,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork
return ReferenceEquals(left, null) || left.Equals(right);
}
protected static bool NotEqualOperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
protected abstract IEnumerable<object> GetAtomicValues();
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
@ -47,7 +44,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork
return !thisValues.MoveNext() && !otherValues.MoveNext();
}
public override int GetHashCode()
{
return GetAtomicValues()


+ 3
- 3
src/Services/Ordering/Ordering.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs View File

@ -7,8 +7,7 @@ using System;
namespace Ordering.Infrastructure.EntityConfigurations
{
class OrderEntityTypeConfiguration
: IEntityTypeConfiguration<Order>
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> orderConfiguration)
{
@ -21,6 +20,7 @@ namespace Ordering.Infrastructure.EntityConfigurations
orderConfiguration.Property(o => o.Id)
.ForSqlServerUseSequenceHiLo("orderseq", OrderingContext.DEFAULT_SCHEMA);
//Address value object persisted as owned entity type supported since EF Core 2.0
orderConfiguration.OwnsOne(o => o.Address);
orderConfiguration.Property<DateTime>("OrderDate").IsRequired();
@ -32,7 +32,7 @@ namespace Ordering.Infrastructure.EntityConfigurations
var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));
// DDD Patterns comment:
//Set as Field (New since EF 1.1) to access the OrderItem collection property through its field
//Set as field (New since EF 1.1) to access the OrderItem collection property through its field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
orderConfiguration.HasOne<PaymentMethod>()


+ 1
- 10
src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs View File

@ -12,22 +12,14 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
{
public class OrderingContext
: DbContext,IUnitOfWork
public class OrderingContext : DbContext, IUnitOfWork
{
public const string DEFAULT_SCHEMA = "ordering";
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
public DbSet<PaymentMethod> Payments { get; set; }
public DbSet<Buyer> Buyers { get; set; }
public DbSet<CardType> CardTypes { get; set; }
public DbSet<OrderStatus> OrderStatus { get; set; }
private readonly IMediator _mediator;
@ -63,7 +55,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
// You will need to handle eventual consistency and compensatory actions in case of failures in any of the Handlers.
await _mediator.DispatchDomainEventsAsync(this);
// After executing this line all the changes (from the Command Handler and Domain Event Handlers)
// performed throught the DbContext will be commited
var result = await base.SaveChangesAsync();


+ 1
- 4
src/Services/Ordering/Ordering.Infrastructure/Repositories/BuyerRepository.cs View File

@ -11,7 +11,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor
: IBuyerRepository
{
private readonly OrderingContext _context;
public IUnitOfWork UnitOfWork
{
get
@ -29,7 +28,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor
{
if (buyer.IsTransient())
{
//TODO: when migrating to ef core 1.1.1 change Add by AddAsync-. A bug in ef core 1.1.0 does not allow to do it https://github.com/aspnet/EntityFramework/issues/7298
return _context.Buyers
.Add(buyer)
.Entity;
@ -37,8 +35,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor
else
{
return buyer;
}
}
}
public Buyer Update(Buyer buyer)


+ 4
- 3
src/Services/Payment/Payment.API/Startup.cs View File

@ -127,6 +127,8 @@ namespace Payment.API
private void RegisterEventBus(IServiceCollection services)
{
var subscriptionClientName = Configuration["SubscriptionClientName"];
if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
@ -134,8 +136,7 @@ namespace Payment.API
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var subscriptionClientName = Configuration["SubscriptionClientName"];
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope);
@ -156,7 +157,7 @@ namespace Payment.API
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, retryCount);
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}


+ 1
- 1
src/Web/WebMVC/Views/Order/Index.cshtml View File

@ -22,7 +22,7 @@
</article>
@if (Model != null && Model.Any())
{
@foreach (var item in Model)
foreach (var item in Model)
{
<article class="esh-orders-items row">
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>


+ 1
- 0
src/Web/WebMVC/Views/Shared/_Layout.cshtml View File

@ -39,6 +39,7 @@
<a asp-area="" asp-controller="Catalog" asp-action="Index">
<img src="~/images/brand.png" />
</a>
</a>
</section>
@await Html.PartialAsync("_LoginPartial")


+ 1
- 1
test/Services/IntegrationTests/Services/Basket/RedisBasketRepositoryTests.cs View File

@ -31,7 +31,7 @@
});
Assert.NotNull(basket);
Assert.Equal(1, basket.Items.Count);
Assert.Single(basket.Items);
}
[Fact]


+ 2
- 2
test/Services/IntegrationTests/Services/Catalog/CatalogScenarios.cs View File

@ -41,7 +41,7 @@ namespace IntegrationTests.Services.Catalog
var response = await server.CreateClient()
.GetAsync(Get.ItemById(int.MinValue));
Assert.Equal(response.StatusCode, HttpStatusCode.BadRequest);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
}
@ -53,7 +53,7 @@ namespace IntegrationTests.Services.Catalog
var response = await server.CreateClient()
.GetAsync(Get.ItemById(int.MaxValue));
Assert.Equal(response.StatusCode, HttpStatusCode.NotFound);
Assert.Equal( HttpStatusCode.NotFound, response.StatusCode);
}
}


+ 3
- 3
test/Services/LoadTest/LoadTest.csproj View File

@ -147,15 +147,15 @@
<None Include="WebMVC\AddProducts.webtest">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="WebMVC\CatalogFilter.webtest">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Identity.API\Logout.webtest">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="WebMVC\CreateNewOrder.webtest">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="WebMVC\CatalogFilter.webtest">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup />
<Choose>


+ 4
- 1
test/Services/LoadTest/OrderProducts.loadtest View File

@ -3,7 +3,10 @@
<Scenarios>
<Scenario Name="OrderProductsLoadTest" DelayBetweenIterations="0" PercentNewUsers="0" IPSwitching="false" TestMixType="PercentageOfTestsStarted" ApplyDistributionToPacingDelay="true" MaxTestIterations="0" DisableDuringWarmup="false" DelayStartTime="0" AllowedAgents="">
<ThinkProfile Value="0.2" Pattern="NormalDistribution" />
<LoadProfile Pattern="Step" InitialUsers="1" MaxUsers="50" StepUsers="2" StepDuration="10" StepRampTime="10" />
<LoadProfile Pattern="Step" InitialUsers="1" MaxUsers="1000" StepUsers="50" StepDuration="10" StepRampTime="10" />
<InitializeTest>
<TestProfile Name="AddProducts" Path="webmvc\addproducts.webtest" Id="2c9d53ae-0237-47bd-a5d2-6500ef5d8fcb" Percentage="0.0" Type="Microsoft.VisualStudio.TestTools.WebStress.DeclarativeWebTestElement, Microsoft.VisualStudio.QualityTools.LoadTest, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</InitializeTest>
<TestMix>
<TestProfile Name="GetItems" Path="catalog.api\getitems.webtest" Id="e527de7e-beff-4824-af52-dda763fd5e6c" Percentage="19" Type="Microsoft.VisualStudio.TestTools.WebStress.DeclarativeWebTestElement, Microsoft.VisualStudio.QualityTools.LoadTest, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<TestProfile Name="GetCatalogTypes" Path="catalog.api\getcatalogtypes.webtest" Id="7df20b29-d5c3-447b-b73d-95c63e9c4061" Percentage="15" Type="Microsoft.VisualStudio.TestTools.WebStress.DeclarativeWebTestElement, Microsoft.VisualStudio.QualityTools.LoadTest, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />


+ 1
- 1
test/Services/LoadTest/WebMVC/CreateNewOrder.webtest View File

@ -164,7 +164,7 @@
<FormPostParameter Name="Total" Value="8.5" RecordedValue="8.5" CorrelationBinding="" UrlEncode="True" />
<FormPostParameter Name="action" Value="[ Place Order ]" RecordedValue="[ Place Order ]" CorrelationBinding="{{FormPostParam9.action}}" UrlEncode="True" />
<FormPostParameter Name="ZipCode" Value="98052" RecordedValue="98052" CorrelationBinding="" UrlEncode="True" />
<FormPostParameter Name="RequestId" Value="{{GenGuid}} " RecordedValue="f58b9345-ea25-4125-a8bf-b0992233af6c" CorrelationBinding="" UrlEncode="True" />
<FormPostParameter Name="RequestId" Value="{{GenGuid}}" RecordedValue="{{GenGuid}}" CorrelationBinding="" UrlEncode="True" />
<FormPostParameter Name="CardTypeId" Value="1" RecordedValue="" CorrelationBinding="" UrlEncode="True" />
</FormPostHttpBody>
</Request>


+ 84
- 0
test/Services/LoadTest/readme.md View File

@ -0,0 +1,84 @@
# Load Testing settings
This folder contains files needed to run load tests locally or on a Kubernetes / Service Fabric cluster.
<p>
<img src="../../../img/loadtests/loadtestproj_dir.png">
<p>
## Set a local environment
Modify the **app.config** file in the LoadTest project directory and set the following service urls.
```
<Servers>
<MvcWebServer url="http://localhost:5100" />
<CatalogApiServer url="http://localhost:5101" />
<OrderingApiServer url="http://localhost:5102" />
<BasketApiServer url="http://localhost:5103" />
<IdentityApiServer url="http://localhost:5105" />
<LocationsApiServer url="http://localhost:5109" />
<MarketingApiServer url="http://localhost:5110" />
</Servers>
```
Modify the **.env** file and set the following config property as shown bellow.
```
USE_LOADTEST=True
```
## Set a Service Fabric environment
Modify the **app.config** file in the LoadTest project directory and set the following service urls.
```
<Servers>
<MvcWebServer url="http://<target_sf_dns>:5100" />
<CatalogApiServer url="http://<target_sf_dns>:5101" />
<OrderingApiServer url="http://<target_sf_dns>:5102" />
<BasketApiServer url="http://<target_sf_dns>:5103" />
<IdentityApiServer url="http://<target_sf_dns>:5105" />
<LocationsApiServer url="http://<target_sf_dns>:5109" />
<MarketingApiServer url="http://<target_sf_dns>:5110" />
</Servers>
```
Modify the **ServiceManifest.xml** files of the eShop SF Services and set the **UseLoadTest** environment variable to True. This setting enables the load tests to bypass authorization in api services.
<p>
<img src="../../../img/loadtests/sfmanifestsettings.png">
<p>
Deploy the SF services. **PLEASE** Read our [SF deployment guide for Linux](./../../../deploy/az/servicefabric/LinuxContainers/readme.md) And [SF deployment guide for Windows](./../../../deploy/az/servicefabric/WindowsContainers/readme.md) to know about how to deploy eshop on SF.
## Set a Kubernetes environment
Modify the **app.config** file in the LoadTest project directory and set the following service urls.
```
<Servers>
<MvcWebServer url="http://<public_ip_k8s>/webmvc" />
<CatalogApiServer url="http://<public_ip_k8s>/catalog-api" />
<OrderingApiServer url="http://<public_ip_k8s>/ordering-api" />
<BasketApiServer url="http://<public_ip_k8s>/basket-api" />
<IdentityApiServer url="http://<public_ip_k8s>/identity" />
<LocationsApiServer url="http://<public_ip_k8s>/locations-api" />
<MarketingApiServer url="http://<public_ip_k8s>/marketing-api" />
</Servers>
```
Modify the **conf_local.yml** file in the K8s directory and set the **EnableLoadTest** environment variable to True. This setting enables the load tests to bypass authorization in api services.
<p>
<img src="../../../img/loadtests/k8ssettings.png">
<p>
Deploy the kubernetes services. **PLEASE** Read our [k8s deployment guide](./../../../k8s/README.k8s.md) to know about how to deploy eshop on Kubernetes.
## Run Load Tests
Open the load test you want to perform ***.loadtest** files and click the Run Load test button.
<p>
<img src="./../../../img/loadtests/runloadtest.png">
<p>

test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs → test/Services/UnitTest/Ordering/Application/IdentifiedCommandHandlerTest.cs View File

@ -13,12 +13,12 @@ namespace UnitTest.Ordering.Application
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
public class IdentifierCommandHandlerTest
public class IdentifiedCommandHandlerTest
{
private readonly Mock<IRequestManager> _requestManager;
private readonly Mock<IMediator> _mediator;
public IdentifierCommandHandlerTest()
public IdentifiedCommandHandlerTest()
{
_requestManager = new Mock<IRequestManager>();
_mediator = new Mock<IMediator>();
@ -38,7 +38,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(true));
//Act
var handler = new IdentifierCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object);
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object);
var result = await handler.Handle(fakeOrderCmd);
//Assert
@ -60,7 +60,7 @@ namespace UnitTest.Ordering.Application
.Returns(Task.FromResult(true));
//Act
var handler = new IdentifierCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object);
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object);
var result = await handler.Handle(fakeOrderCmd);
//Assert

+ 3
- 3
test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs View File

@ -93,7 +93,7 @@ namespace UnitTest.Ordering.Application
public async Task Get_orders_success()
{
//Arrange
var fakeDynamicResult = Enumerable.Empty<object>();
var fakeDynamicResult = Enumerable.Empty<OrderSummary>();
_orderQueriesMock.Setup(x => x.GetOrdersAsync())
.Returns(Task.FromResult(fakeDynamicResult));
@ -110,7 +110,7 @@ namespace UnitTest.Ordering.Application
{
//Arrange
var fakeOrderId = 123;
var fakeDynamicResult = new Object();
var fakeDynamicResult = new Order();
_orderQueriesMock.Setup(x => x.GetOrderAsync(It.IsAny<int>()))
.Returns(Task.FromResult(fakeDynamicResult));
@ -126,7 +126,7 @@ namespace UnitTest.Ordering.Application
public async Task Get_cardTypes_success()
{
//Arrange
var fakeDynamicResult = Enumerable.Empty<object>();
var fakeDynamicResult = Enumerable.Empty<CardType>();
_orderQueriesMock.Setup(x => x.GetCardTypesAsync())
.Returns(Task.FromResult(fakeDynamicResult));


+ 0
- 3
test/Services/UnitTest/Ordering/Domain/OrderAggregateTest.cs View File

@ -111,7 +111,6 @@ public class OrderAggregateTest
public void Add_new_Order_raises_new_event()
{
//Arrange
var userId = new Guid();
var street = "fakeStreet";
var city = "FakeCity";
var state = "fakeState";
@ -135,7 +134,6 @@ public class OrderAggregateTest
public void Add_event_Order_explicitly_raises_new_event()
{
//Arrange
var userId = new Guid();
var street = "fakeStreet";
var city = "FakeCity";
var state = "fakeState";
@ -159,7 +157,6 @@ public class OrderAggregateTest
public void Remove_event_Order_explicitly()
{
//Arrange
var userId = new Guid();
var street = "fakeStreet";
var city = "FakeCity";
var state = "fakeState";


Loading…
Cancel
Save