Browse Source

Merge branch 'master' into xamarin

pull/223/head
David Britch 7 years ago
parent
commit
e7cacd7063
53 changed files with 577 additions and 333 deletions
  1. +7
    -6
      README.md
  2. BIN
      img/ebook_arch_dev_microservices_containers_cover.png
  3. BIN
      img/ebook_arch_dev_microservices_containers_cover_LARGE.png
  4. BIN
      img/ebook_arch_dev_microservices_containers_cover_OLD.png
  5. +0
    -3
      src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs
  6. +0
    -3
      src/BuildingBlocks/EventBus/EventBus/Abstractions/IIntegrationEventHandler.cs
  7. +0
    -2
      src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs
  8. +130
    -0
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs
  9. +110
    -68
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  10. +2
    -0
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj
  11. +16
    -0
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs
  12. +1
    -6
      src/Services/Basket/Basket.API/BasketSettings.cs
  13. +0
    -4
      src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs
  14. +2
    -3
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs
  15. +7
    -2
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs
  16. +0
    -4
      src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs
  17. +0
    -3
      src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs
  18. +2
    -4
      src/Services/Basket/Basket.API/Model/IBasketRepository.cs
  19. +23
    -15
      src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs
  20. +51
    -27
      src/Services/Basket/Basket.API/Startup.cs
  21. +9
    -0
      src/Services/Catalog/Catalog.API/CatalogSettings.cs
  22. +69
    -48
      src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs
  23. +1
    -5
      src/Services/Catalog/Catalog.API/Controllers/HomeController.cs
  24. +5
    -7
      src/Services/Catalog/Catalog.API/Controllers/PicController.cs
  25. +0
    -4
      src/Services/Catalog/Catalog.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs
  26. +0
    -3
      src/Services/Catalog/Catalog.API/Infrastructure/Exceptions/CatalogDomainException.cs
  27. +1
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs
  28. +0
    -3
      src/Services/Catalog/Catalog.API/IntegrationEvents/ICatalogIntegrationEventService.cs
  29. +0
    -6
      src/Services/Catalog/Catalog.API/Model/CatalogBrand.cs
  30. +0
    -5
      src/Services/Catalog/Catalog.API/Model/CatalogType.cs
  31. +32
    -18
      src/Services/Catalog/Catalog.API/Startup.cs
  32. +0
    -14
      src/Services/Catalog/Catalog.API/settings.cs
  33. +5
    -0
      src/Services/Identity/Identity.API/Startup.cs
  34. +4
    -2
      src/Services/Ordering/Ordering.API/Application/Commands/IdentifierCommandHandler.cs
  35. +3
    -3
      src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs
  36. +3
    -3
      src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs
  37. +11
    -13
      src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs
  38. +1
    -5
      src/Services/Ordering/Ordering.API/Application/Validations/IdentifierCommandValidator.cs
  39. +7
    -3
      src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
  40. +3
    -3
      src/Services/Ordering/Ordering.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
  41. +24
    -9
      src/Services/Ordering/Ordering.API/Startup.cs
  42. +4
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs
  43. +2
    -2
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/IOrderRepository.cs
  44. +3
    -3
      src/Services/Ordering/Ordering.Domain/SeedWork/Enumeration.cs
  45. +1
    -3
      src/Services/Ordering/Ordering.Infrastructure/Idempotency/ClientRequest.cs
  46. +1
    -2
      src/Services/Ordering/Ordering.Infrastructure/Idempotency/IRequestManager.cs
  47. +10
    -10
      src/Services/Ordering/Ordering.Infrastructure/Idempotency/RequestManager.cs
  48. +11
    -4
      src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs
  49. +2
    -1
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  50. +5
    -0
      src/Web/WebMVC/Startup.cs
  51. +1
    -1
      src/Web/WebSPA/Client/modules/app.module.ts
  52. +5
    -0
      src/Web/WebSPA/Startup.cs
  53. +3
    -3
      test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs

+ 7
- 6
README.md View File

@ -1,12 +1,13 @@
# eShopOnContainers - Microservices Architecture and Containers based Reference Application (**ALPHA state** - VS 2017 and CLI environments compatible)
# eShopOnContainers - Microservices Architecture and Containers based Reference Application (**BETA state** - Visual Studio 2017 and CLI environments compatible)
Sample .NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers. <p>
> ### DISCLAIMER
> **IMPORTANT:** The current state of this sample application is **ALPHA**, consider it version a 0.1 foundational version, therefore, many areas could be improved and change significantly while refactoring current code and implementing new features. **Feedback with improvements and pull requests from the community will be highly appreciated and accepted.**
> **IMPORTANT:** The current state of this sample application is **BETA**, consider it version a 0.1 foundational version, therefore, many areas could be improved and change significantly while refactoring current code and implementing new features. **Feedback with improvements and pull requests from the community will be highly appreciated and accepted.**
>
> This reference application proposes a simplified microservice oriented architecture implementation to introduce technologies like .NET Core with Docker containers through a comprehensive application. However, this reference application it is not trying to solve all the problems in a large and mission-critical distributed system, it is just a bootstrap for developers to easily get started in the world of Docker containers and microservices with .NET Core.
> This reference application proposes a simplified microservice oriented architecture implementation to introduce technologies like .NET Core with Docker containers through a comprehensive application. The chosen domain is an eShop/eCommerce but simply because it is a well-know domain by most people/developers.
However, this sample application should not be considered as an "eCommerce reference model", at all. The implemented business domain might not be ideal from an eCommerce business point of view. It is neither trying to solve all the problems in a large, scalable and mission-critical distributed system. It is just a bootstrap for developers to easily get started in the world of Docker containers and microservices with .NET Core.
> <p>For example, the next step (still not covered in eShopOnContainers) after understanding Docker containers and microservices development with .NET Core, is to select a microservice cluster/orchestrator like Docker Swarm, Kubernetes or DC/OS (in Azure Container Service) or Azure Service Fabric which in most of the cases will require additional partial changes to your application's configuration (although the present architecture should work on most orchestrators with small changes).
> Or moving your databases to HA cloud services, or implementing your EventBus with Azure Service Bus or any other production ready Service Bus in the market.
> Additional steps would be to move your databases to HA cloud services, or to implement your EventBus with Azure Service Bus or any other production ready Service Bus in the market.
> <p> In the future we might fork this project and make multiple versions targeting specific microservice cluster/orchestrators plus using additional cloud infrastructure. <p>
> <img src="img/exploring-to-production-ready.png">
> Read the planned <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>Roadmap and Milestones for future releases of eShopOnContainers</a> within the Wiki for further info about possible new implementations and provide feedback at the <a href='https://github.com/dotnet/eShopOnContainers/issues'>ISSUES section</a> if you'd like to see any specific scenario implemented or improved. Also, feel free to discuss on any current issue.
@ -32,7 +33,7 @@ Additional miroservice styles with other frameworks and No-SQL databases will be
> <p> However, in a real production environment it is recommended to have your databases (SQL Server and Redis, in this case) in HA (High Available) services like Azure SQL Database, Redis as a service or any other clustering system. If you want to change to a production configuration, you'll just need to change the connection strings once you have set up the servers in a HA cloud or on-premises.
## Related documentation and guidance
While developing this reference application, we are creating a reference Guide/eBook named <b>"Architecting and Developing Containerized and Microservice based .NET Applications"</b> which explains in detail how to develop this kind of architectural style (microservices, Docker containers, Domain-Driven Design for certain microservices) plus other simpler architectural styles, like monolithic apps that can also live as Docker containers.
While developing this reference application, we've been creating a reference <b>Guide/eBook</b> focusing on <b>architecting and developing containerized and microservice based .NET Applications</b> (download link available below) which explains in detail how to develop this kind of architectural style (microservices, Docker containers, Domain-Driven Design for certain microservices) plus other simpler architectural styles, like monolithic apps that can also live as Docker containers.
<p>
There are also additional eBooks focusing on Containers/Docker lifecycle (DevOps, CI/CD, etc.) with Microsoft Tools, already published plus an additional eBook focusing on Enterprise Apps Patterns with Xamarin.Forms.
You can download them and start reviewing these Guides/eBooks here:
@ -43,7 +44,7 @@ You can download them and start reviewing these Guides/eBooks here:
| <a href='https://aka.ms/microservicesebook'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> | <a href='https://aka.ms/xamarinpatternsebook'> <img src="img/xamarin-enterprise-patterns-ebook-cover-small.png"> </a> |
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (Early DRAFT, still work in progress)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (Early DRAFT, still work in progress) </a> </sup> |
Send feedback to [cesardl@microsoft.com](cesardl@microsoft.com)
Send feedback to [dotnet-architecture-ebooks-feedback@service.microsoft.com](dotnet-architecture-ebooks-feedback@service.microsoft.com)
<p>
However, we encourage to download and review the "Architecting & Developing eBook" because the architectural styles and architectural patterns and technologies explained in the guidance are using this reference application when explaining many pattern implementations, so you'll understand much better the context, design and decisions taken in the current architecture and internal designs.


BIN
img/ebook_arch_dev_microservices_containers_cover.png View File

Before After
Width: 257  |  Height: 336  |  Size: 31 KiB Width: 260  |  Height: 336  |  Size: 38 KiB

BIN
img/ebook_arch_dev_microservices_containers_cover_LARGE.png View File

Before After
Width: 1139  |  Height: 1469  |  Size: 242 KiB

BIN
img/ebook_arch_dev_microservices_containers_cover_OLD.png View File

Before After
Width: 257  |  Height: 336  |  Size: 31 KiB

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

@ -1,7 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions
{


+ 0
- 3
src/BuildingBlocks/EventBus/EventBus/Abstractions/IIntegrationEventHandler.cs View File

@ -1,7 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions


+ 0
- 2
src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs View File

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events
{


+ 130
- 0
src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs View File

@ -0,0 +1,130 @@
using Microsoft.Extensions.Logging;
using Polly;
using Polly.Retry;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using System;
using System.IO;
using System.Net.Sockets;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public class DefaultRabbitMQPersisterConnection
: IRabbitMQPersisterConnection
{
private readonly IConnectionFactory _connectionFactory;
private readonly ILogger<DefaultRabbitMQPersisterConnection> _logger;
IConnection _connection;
bool _disposed;
object sync_root = new object();
public DefaultRabbitMQPersisterConnection(IConnectionFactory connectionFactory,ILogger<DefaultRabbitMQPersisterConnection> logger)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public bool IsConnected
{
get
{
return _connection != null && _connection.IsOpen && !_disposed;
}
}
public IModel CreateModel()
{
if (!IsConnected)
{
throw new InvalidOperationException("No RabbitMQ connections are available to perform this action");
}
return _connection.CreateModel();
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
try
{
_connection.Dispose();
}
catch (IOException ex)
{
_logger.LogCritical(ex.ToString());
}
}
public bool TryConnect()
{
_logger.LogInformation("RabbitMQ Client is trying to connect");
lock (sync_root)
{
var policy = RetryPolicy.Handle<SocketException>()
.Or<BrokerUnreachableException>()
.WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{
_logger.LogWarning(ex.ToString());
}
);
policy.Execute(() =>
{
_connection = _connectionFactory
.CreateConnection();
});
if (IsConnected)
{
_connection.ConnectionShutdown += OnConnectionShutdown;
_connection.CallbackException += OnCallbackException;
_connection.ConnectionBlocked += OnConnectionBlocked;
_logger.LogInformation($"RabbitMQ persister connection acquire a connection {_connection.Endpoint.HostName} and is subscribed to failure events");
return true;
}
else
{
_logger.LogCritical("FATAL ERROR: RabbitMQ connections can't be created and opened");
return false;
}
}
}
private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e)
{
if (_disposed) return;
_logger.LogWarning("A RabbitMQ connection is shutdown. Trying to re-connect...");
TryConnect();
}
void OnCallbackException(object sender, CallbackExceptionEventArgs e)
{
if (_disposed) return;
_logger.LogWarning("A RabbitMQ connection throw exception. Trying to re-connect...");
TryConnect();
}
void OnConnectionShutdown(object sender, ShutdownEventArgs reason)
{
if (_disposed) return;
_logger.LogWarning("A RabbitMQ connection is on shutdown. Trying to re-connect...");
TryConnect();
}
}
}

+ 110
- 68
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs View File

@ -1,13 +1,16 @@

using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
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;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@ -16,68 +19,98 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public class EventBusRabbitMQ : IEventBus, IDisposable
{
private readonly string _brokerName = "eshop_event_bus";
private readonly string _connectionString;
private readonly Dictionary<string, List<IIntegrationEventHandler>> _handlers;
private readonly List<Type> _eventTypes;
const string BROKER_NAME = "eshop_event_bus";
private IModel _model;
private IConnection _connection;
private readonly IRabbitMQPersisterConnection _persisterConnection;
private readonly ILogger<EventBusRabbitMQ> _logger;
private readonly Dictionary<string, List<IIntegrationEventHandler>> _handlers
= new Dictionary<string, List<IIntegrationEventHandler>>();
private readonly List<Type> _eventTypes
= new List<Type>();
private IModel _consumerChannel;
private string _queueName;
public EventBusRabbitMQ(string connectionString)
public EventBusRabbitMQ(IRabbitMQPersisterConnection persisterConnection, ILogger<EventBusRabbitMQ> logger)
{
_connectionString = connectionString;
_handlers = new Dictionary<string, List<IIntegrationEventHandler>>();
_eventTypes = new List<Type>();
_persisterConnection = persisterConnection ?? throw new ArgumentNullException(nameof(persisterConnection));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_consumerChannel = CreateConsumerChannel();
}
public void Publish(IntegrationEvent @event)
{
var eventName = @event.GetType().Name;
var factory = new ConnectionFactory() { HostName = _connectionString };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
if (!_persisterConnection.IsConnected)
{
channel.ExchangeDeclare(exchange: _brokerName,
_persisterConnection.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 = _persisterConnection.CreateModel())
{
var eventName = @event.GetType()
.Name;
channel.ExchangeDeclare(exchange: BROKER_NAME,
type: "direct");
string message = JsonConvert.SerializeObject(@event);
var message = JsonConvert.SerializeObject(@event);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: _brokerName,
policy.Execute(() =>
{
channel.BasicPublish(exchange: BROKER_NAME,
routingKey: eventName,
basicProperties: null,
body: body);
body: body);
});
}
}
public void Subscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
{
var eventName = typeof(T).Name;
if (_handlers.ContainsKey(eventName))
if (_handlers.ContainsKey(eventName))
{
_handlers[eventName].Add(handler);
}
else
{
var channel = GetChannel();
channel.QueueBind(queue: _queueName,
exchange: _brokerName,
routingKey: eventName);
_handlers.Add(eventName, new List<IIntegrationEventHandler>());
_handlers[eventName].Add(handler);
_eventTypes.Add(typeof(T));
if (!_persisterConnection.IsConnected)
{
_persisterConnection.TryConnect();
}
using (var channel = _persisterConnection.CreateModel())
{
channel.QueueBind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
_handlers.Add(eventName, new List<IIntegrationEventHandler>());
_handlers[eventName].Add(handler);
_eventTypes.Add(typeof(T));
}
}
}
public void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
{
var eventName = typeof(T).Name;
if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler))
{
_handlers[eventName].Remove(handler);
@ -85,56 +118,59 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
if (_handlers[eventName].Count == 0)
{
_handlers.Remove(eventName);
var eventType = _eventTypes.Single(e => e.Name == eventName);
_eventTypes.Remove(eventType);
_model.QueueUnbind(queue: _queueName,
exchange: _brokerName,
routingKey: eventName);
if (_handlers.Keys.Count == 0)
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null)
{
_queueName = string.Empty;
_model.Dispose();
_connection.Dispose();
_eventTypes.Remove(eventType);
if (!_persisterConnection.IsConnected)
{
_persisterConnection.TryConnect();
}
using (var channel = _persisterConnection.CreateModel())
{
channel.QueueUnbind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
if (_handlers.Keys.Count == 0)
{
_queueName = string.Empty;
_consumerChannel.Close();
}
}
}
}
}
}
public void Dispose()
{
if (_consumerChannel != null)
{
_consumerChannel.Dispose();
}
_handlers.Clear();
_model?.Dispose();
_connection?.Dispose();
}
private IModel GetChannel()
private IModel CreateConsumerChannel()
{
if (_model != null)
if (!_persisterConnection.IsConnected)
{
return _model;
_persisterConnection.TryConnect();
}
else
{
(_model, _connection) = CreateConnection();
return _model;
}
}
var channel = _persisterConnection.CreateModel();
private (IModel model, IConnection connection) CreateConnection()
{
var factory = new ConnectionFactory() { HostName = _connectionString };
var con = factory.CreateConnection();
var channel = con.CreateModel();
channel.ExchangeDeclare(exchange: BROKER_NAME,
type: "direct");
channel.ExchangeDeclare(exchange: _brokerName,
type: "direct");
if (string.IsNullOrEmpty(_queueName))
{
_queueName = channel.QueueDeclare().QueueName;
}
_queueName = channel.QueueDeclare().QueueName;
var consumer = new EventingBasicConsumer(channel);
consumer.Received += async (model, ea) =>
@ -144,11 +180,18 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
await ProcessEvent(eventName, message);
};
channel.BasicConsume(queue: _queueName,
noAck: true,
consumer: consumer);
return (channel, con);
channel.CallbackException += (sender, ea) =>
{
_consumerChannel.Dispose();
_consumerChannel = CreateConsumerChannel();
};
return channel;
}
private async Task ProcessEvent(string eventName, string message)
@ -156,7 +199,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
if (_handlers.ContainsKey(eventName))
{
Type eventType = _eventTypes.Single(t => t.Name == eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
var handlers = _handlers[eventName];
@ -166,6 +209,5 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
}
}
}
}
}

+ 2
- 0
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj View File

@ -7,7 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Polly" Version="5.0.6" />
<PackageReference Include="RabbitMQ.Client" Version="4.1.1" />
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
</ItemGroup>


+ 16
- 0
src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs View File

@ -0,0 +1,16 @@
using RabbitMQ.Client;
using System;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
{
public interface IRabbitMQPersisterConnection
: IDisposable
{
bool IsConnected { get; }
bool TryConnect();
IModel CreateModel();
}
}

+ 1
- 6
src/Services/Basket/Basket.API/BasketSettings.cs View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API
namespace Microsoft.eShopOnContainers.Services.Basket.API
{
public class BasketSettings
{


+ 0
- 4
src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs View File

@ -1,9 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Basket.API.Infrastructure.ActionResults
{


+ 2
- 3
src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs View File

@ -2,8 +2,6 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Basket.API.IntegrationEvents.EventHandling
@ -11,9 +9,10 @@ namespace Basket.API.IntegrationEvents.EventHandling
public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent>
{
private readonly IBasketRepository _repository;
public OrderStartedIntegrationEventHandler(IBasketRepository repository)
{
_repository = repository;
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task Handle(OrderStartedIntegrationEvent @event)


+ 7
- 2
src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs View File

@ -1,6 +1,7 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using System;
using System.Linq;
using System.Threading.Tasks;
@ -9,17 +10,20 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even
public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>
{
private readonly IBasketRepository _repository;
public ProductPriceChangedIntegrationEventHandler(IBasketRepository repository)
{
_repository = repository;
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task Handle(ProductPriceChangedIntegrationEvent @event)
{
var userIds = await _repository.GetUsers();
var userIds = await _repository.GetUsersAsync();
foreach (var id in userIds)
{
var basket = await _repository.GetBasketAsync(id);
await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket);
}
}
@ -27,6 +31,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even
private async Task UpdatePriceInBasketItems(int productId, decimal newPrice, decimal oldPrice, CustomerBasket basket)
{
var itemsToUpdate = basket?.Items?.Where(x => int.Parse(x.ProductId) == productId).ToList();
if (itemsToUpdate != null)
{
foreach (var item in itemsToUpdate)


+ 0
- 4
src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs View File

@ -1,8 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Basket.API.IntegrationEvents.Events
{


+ 0
- 3
src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs View File

@ -1,7 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events
{


+ 2
- 4
src/Services/Basket/Basket.API/Model/IBasketRepository.cs View File

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
@ -8,7 +6,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
public interface IBasketRepository
{
Task<CustomerBasket> GetBasketAsync(string customerId);
Task<IEnumerable<string>> GetUsers();
Task<IEnumerable<string>> GetUsersAsync();
Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket);
Task<bool> DeleteBasketAsync(string id);
}


+ 23
- 15
src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs View File

@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using StackExchange.Redis;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
{
@ -31,7 +30,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
return await database.KeyDeleteAsync(id.ToString());
}
public async Task<IEnumerable<string>> GetUsers()
public async Task<IEnumerable<string>> GetUsersAsync()
{
var server = await GetServer();
@ -63,11 +62,12 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
var created = await database.StringSetAsync(basket.BuyerId, JsonConvert.SerializeObject(basket));
if (!created)
{
_logger.LogInformation("Problem persisting the item");
_logger.LogInformation("Problem occur persisting the item.");
return null;
}
_logger.LogInformation("basket item persisted succesfully");
_logger.LogInformation("Basket item persisted succesfully.");
return await GetBasketAsync(basket.BuyerId);
}
@ -94,13 +94,21 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
private async Task ConnectToRedisAsync()
{
//TODO: Need to make this more robust. Also want to understand why the static connection method cannot accept dns names.
var ips = await Dns.GetHostAddressesAsync(_settings.ConnectionString);
_logger.LogInformation($"Connecting to database {_settings.ConnectionString} at IP {ips.First().ToString()}");
_redis = await ConnectionMultiplexer.ConnectAsync(ips.First().ToString());
// TODO: Need to make this more robust. ConnectionMultiplexer.ConnectAsync doesn't like domain names or IPv6 addresses.
if (IPAddress.TryParse(_settings.ConnectionString, out var ip))
{
_redis = await ConnectionMultiplexer.ConnectAsync(ip.ToString());
_logger.LogInformation($"Connecting to database at {_settings.ConnectionString}");
}
else
{
// workaround for https://github.com/StackExchange/StackExchange.Redis/issues/410
var ips = await Dns.GetHostAddressesAsync(_settings.ConnectionString);
_logger.LogInformation($"Connecting to database {_settings.ConnectionString} at IP {ips.First().ToString()}");
_redis = await ConnectionMultiplexer.ConnectAsync(ips.First().ToString());
}
}
}
}

+ 51
- 27
src/Services/Basket/Basket.API/Startup.cs View File

@ -1,24 +1,24 @@
using System.Linq;
using Basket.API.Infrastructure.Filters;
using Basket.API.IntegrationEvents.EventHandling;
using Basket.API.IntegrationEvents.Events;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling;
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using StackExchange.Redis;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using StackExchange.Redis;
using System.Linq;
using System.Net;
using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using System;
using Microsoft.Extensions.HealthChecks;
using System.Threading.Tasks;
using Basket.API.Infrastructure.Filters;
using Basket.API.IntegrationEvents.Events;
using Basket.API.IntegrationEvents.EventHandling;
namespace Microsoft.eShopOnContainers.Services.Basket.API
{
@ -59,17 +59,33 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
//and then creating the connection it seems reasonable to move
//that cost to startup instead of having the first request pay the
//penalty.
services.AddSingleton<ConnectionMultiplexer>((sp) => {
var config = sp.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
var ips = Dns.GetHostAddressesAsync(config.ConnectionString).Result;
services.AddSingleton<ConnectionMultiplexer>(sp =>
{
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
var ips = Dns.GetHostAddressesAsync(settings.ConnectionString).Result;
return ConnectionMultiplexer.Connect(ips.First().ToString());
});
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
{
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
return new DefaultRabbitMQPersisterConnection(factory, logger);
});
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddSwaggerGen();
//var sch = new IdentitySecurityScheme();
services.ConfigureSwaggerGen(options =>
{
//options.AddSecurityDefinition("IdentityServer", sch);
options.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
options.DescribeAllEnumsAsStrings();
options.SingleApiVersion(new Swashbuckle.Swagger.Model.Info()
@ -95,9 +111,6 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<IIntegrationEventHandler<OrderStartedIntegrationEvent>, OrderStartedIntegrationEventHandler>();
var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
services.AddSingleton<IEventBus>(provider => new EventBusRabbitMQ(configuration.EventBusConnection));
}
@ -119,11 +132,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
app.UseSwagger()
.UseSwaggerUi();
var catalogPriceHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>();
var orderStartedHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<OrderStartedIntegrationEvent>>();
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
eventBus.Subscribe<OrderStartedIntegrationEvent>(orderStartedHandler);
ConfigureEventBus(app);
}
@ -136,6 +145,21 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
ScopeName = "basket",
RequireHttpsMetadata = false
});
}
}
protected virtual void ConfigureEventBus(IApplicationBuilder app)
{
var catalogPriceHandler = app.ApplicationServices
.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>();
var orderStartedHandler = app.ApplicationServices
.GetService<IIntegrationEventHandler<OrderStartedIntegrationEvent>>();
var eventBus = app.ApplicationServices
.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
eventBus.Subscribe<OrderStartedIntegrationEvent>(orderStartedHandler);
}
}
}

+ 9
- 0
src/Services/Catalog/Catalog.API/CatalogSettings.cs View File

@ -0,0 +1,9 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API
{
public class CatalogSettings
{
public string ExternalCatalogBaseUrl {get;set;}
public string EventBusConnection { get; set; }
}
}

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

@ -1,9 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Catalog.API.IntegrationEvents;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
@ -11,12 +8,8 @@ using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
using Catalog.API.IntegrationEvents;
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{
@ -24,16 +17,16 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
public class CatalogController : ControllerBase
{
private readonly CatalogContext _catalogContext;
private readonly IOptionsSnapshot<Settings> _settings;
private readonly CatalogSettings _settings;
private readonly ICatalogIntegrationEventService _catalogIntegrationEventService;
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, ICatalogIntegrationEventService catalogIntegrationEventService)
public CatalogController(CatalogContext context, IOptionsSnapshot<CatalogSettings> settings, ICatalogIntegrationEventService catalogIntegrationEventService)
{
_catalogContext = Context;
_catalogIntegrationEventService = catalogIntegrationEventService;
_settings = settings;
_catalogContext = context ?? throw new ArgumentNullException(nameof(context));
_catalogIntegrationEventService = catalogIntegrationEventService ?? throw new ArgumentNullException(nameof(catalogIntegrationEventService));
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
_settings = settings.Value;
((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
// GET api/v1/[controller]/items[?pageSize=3&pageIndex=10]
@ -46,19 +39,37 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
.LongCountAsync();
var itemsOnPage = await _catalogContext.CatalogItems
.OrderBy(c=>c.Name)
.OrderBy(c => c.Name)
.Skip(pageSize * pageIndex)
.Take(pageSize)
.ToListAsync();
itemsOnPage = ComposePicUri(itemsOnPage);
itemsOnPage = ChangeUriPlaceholder(itemsOnPage);
var model = new PaginatedItemsViewModel<CatalogItem>(
pageIndex, pageSize, totalItems, itemsOnPage);
pageIndex, pageSize, totalItems, itemsOnPage);
return Ok(model);
}
[HttpGet]
[Route("items/{id:int}")]
public async Task<IActionResult> GetItemById(int id)
{
if (id <= 0)
{
return BadRequest();
}
var item = await _catalogContext.CatalogItems.SingleOrDefaultAsync(ci => ci.Id == id);
if (item != null)
{
return Ok(item);
}
return NotFound();
}
// GET api/v1/[controller]/items/withname/samplename[?pageSize=3&pageIndex=10]
[HttpGet]
[Route("[action]/withname/{name:minlength(1)}")]
@ -75,7 +86,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
.Take(pageSize)
.ToListAsync();
itemsOnPage = ComposePicUri(itemsOnPage);
itemsOnPage = ChangeUriPlaceholder(itemsOnPage);
var model = new PaginatedItemsViewModel<CatalogItem>(
pageIndex, pageSize, totalItems, itemsOnPage);
@ -108,7 +119,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
.Take(pageSize)
.ToListAsync();
itemsOnPage = ComposePicUri(itemsOnPage);
itemsOnPage = ChangeUriPlaceholder(itemsOnPage);
var model = new PaginatedItemsViewModel<CatalogItem>(
pageIndex, pageSize, totalItems, itemsOnPage);
@ -138,16 +149,23 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
return Ok(items);
}
//POST api/v1/[controller]/update
[Route("update")]
[HttpPost]
//PUT api/v1/[controller]/items
[Route("items")]
[HttpPut]
public async Task<IActionResult> UpdateProduct([FromBody]CatalogItem productToUpdate)
{
var catalogItem = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id);
if (catalogItem == null) return NotFound();
var raiseProductPriceChangedEvent = catalogItem.Price != productToUpdate.Price;
var catalogItem = await _catalogContext.CatalogItems
.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id);
if (catalogItem == null)
{
return NotFound(new { Message = $"Item with id {productToUpdate.Id} not found." });
}
var oldPrice = catalogItem.Price;
var raiseProductPriceChangedEvent = oldPrice != productToUpdate.Price;
// Update current product
catalogItem = productToUpdate;
_catalogContext.CatalogItems.Update(catalogItem);
@ -156,40 +174,40 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{
//Create Integration Event to be published through the Event Bus
var priceChangedEvent = new ProductPriceChangedIntegrationEvent(catalogItem.Id, productToUpdate.Price, oldPrice);
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(priceChangedEvent);
// Publish through the Event Bus and mark the saved event as published
await _catalogIntegrationEventService.PublishThroughEventBusAsync(priceChangedEvent);
}
else // Save updated product
{
await _catalogContext.SaveChangesAsync();
}
}
return Ok();
return CreatedAtAction(nameof(GetItemById), new { id = productToUpdate.Id }, null);
}
//POST api/v1/[controller]/create
[Route("create")]
//POST api/v1/[controller]/items
[Route("items")]
[HttpPost]
public async Task<IActionResult> CreateProduct([FromBody]CatalogItem product)
{
_catalogContext.CatalogItems.Add(
new CatalogItem
{
CatalogBrandId = product.CatalogBrandId,
CatalogTypeId = product.CatalogTypeId,
Description = product.Description,
Name = product.Name,
PictureUri = product.PictureUri,
Price = product.Price
});
var item = new CatalogItem
{
CatalogBrandId = product.CatalogBrandId,
CatalogTypeId = product.CatalogTypeId,
Description = product.Description,
Name = product.Name,
PictureUri = product.PictureUri,
Price = product.Price
};
_catalogContext.CatalogItems.Add(item);
await _catalogContext.SaveChangesAsync();
return Ok();
return CreatedAtAction(nameof(GetItemById), new { id = item.Id }, null);
}
//DELETE api/v1/[controller]/id
@ -202,16 +220,19 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
if (product == null)
{
return NotFound();
}
}
_catalogContext.CatalogItems.Remove(product);
await _catalogContext.SaveChangesAsync();
return Ok();
return NoContent();
}
private List<CatalogItem> ComposePicUri(List<CatalogItem> items) {
var baseUri = _settings.Value.ExternalCatalogBaseUrl;
private List<CatalogItem> ChangeUriPlaceholder(List<CatalogItem> items)
{
var baseUri = _settings.ExternalCatalogBaseUrl;
items.ForEach(x =>
{
x.PictureUri = x.PictureUri.Replace("http://externalcatalogbaseurltobereplaced", baseUri);


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

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860


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

@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using Microsoft.AspNetCore.Hosting;
// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
@ -25,8 +21,10 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{
var webRoot = _env.WebRootPath;
var path = Path.Combine(webRoot, id + ".png");
Byte[] b = System.IO.File.ReadAllBytes(path);
return File(b, "image/png");
var buffer = System.IO.File.ReadAllBytes(path);
return File(buffer, "image/png");
}
}
}

+ 0
- 4
src/Services/Catalog/Catalog.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs View File

@ -1,9 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Catalog.API.Infrastructure.ActionResults
{


+ 0
- 3
src/Services/Catalog/Catalog.API/Infrastructure/Exceptions/CatalogDomainException.cs View File

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Catalog.API.Infrastructure.Exceptions
{


+ 1
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs View File

@ -30,6 +30,7 @@ namespace Catalog.API.IntegrationEvents
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
{
_eventBus.Publish(evt);
await _eventLogService.MarkEventAsPublishedAsync(evt);
}


+ 0
- 3
src/Services/Catalog/Catalog.API/IntegrationEvents/ICatalogIntegrationEventService.cs View File

@ -1,7 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Catalog.API.IntegrationEvents


+ 0
- 6
src/Services/Catalog/Catalog.API/Model/CatalogBrand.cs View File

@ -1,11 +1,5 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class CatalogBrand
{
public int Id { get; set; }


+ 0
- 5
src/Services/Catalog/Catalog.API/Model/CatalogType.cs View File

@ -1,10 +1,5 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class CatalogType
{
public int Id { get; set; }


+ 32
- 18
src/Services/Catalog/Catalog.API/Startup.cs View File

@ -1,6 +1,7 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API
{
using global::Catalog.API.Infrastructure.Filters;
using global::Catalog.API.IntegrationEvents;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
@ -15,13 +16,11 @@
using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using System;
using System.Data.SqlClient;
using System.IO;
using System.Data.Common;
using System.Data.SqlClient;
using System.Reflection;
using global::Catalog.API.IntegrationEvents;
using System.Threading.Tasks;
public class Startup
{
@ -47,7 +46,7 @@
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddHealthChecks(checks =>
{
checks.AddSqlCheck("CatalogDb", Configuration["ConnectionString"]);
@ -62,18 +61,19 @@
{
options.UseSqlServer(Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions =>
{
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
// Changing default behavior when client evaluation occurs to throw.
// Default in EF Core would be to log a warning when client evaluation is performed.
options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
});
services.Configure<Settings>(Configuration);
services.Configure<CatalogSettings>(Configuration);
// Add framework services.
services.AddSwaggerGen();
@ -99,11 +99,23 @@
});
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c));
var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
sp => (DbConnection c) => new IntegrationEventLogService(c));
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
{
var settings = sp.GetRequiredService<IOptions<CatalogSettings>>().Value;
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
return new DefaultRabbitMQPersisterConnection(factory, logger);
});
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
@ -124,25 +136,28 @@
.ApplicationServices.GetService(typeof(CatalogContext));
WaitForSqlAvailability(context, loggerFactory);
//Seed Data
CatalogContextSeed.SeedAsync(app, loggerFactory)
.Wait();
.Wait();
var integrationEventLogContext = new IntegrationEventLogContext(
new DbContextOptionsBuilder<IntegrationEventLogContext>()
.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API"))
.Options);
integrationEventLogContext.Database.Migrate();
}
private void WaitForSqlAvailability(CatalogContext ctx, ILoggerFactory loggerFactory, int? retry = 0)
{
int retryForAvailability = retry.Value;
int retryForAvailability = retry.Value;
try
{
ctx.Database.OpenConnection();
}
catch(SqlException ex)
catch (SqlException ex)
{
if (retryForAvailability < 10)
{
@ -152,11 +167,10 @@
WaitForSqlAvailability(ctx, loggerFactory, retryForAvailability);
}
}
finally {
ctx.Database.CloseConnection();
finally
{
ctx.Database.CloseConnection();
}
}
}
}

+ 0
- 14
src/Services/Catalog/Catalog.API/settings.cs View File

@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Catalog.API
{
// TODO: Rename CatalogSettings for consistency?
public class Settings
{
public string ExternalCatalogBaseUrl {get;set;}
public string EventBusConnection { get; set; }
}
}

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

@ -57,6 +57,11 @@ namespace eShopOnContainers.Identity
services.Configure<AppSettings>(Configuration);
services.AddDataProtection(opts =>
{
opts.ApplicationDiscriminator = "eshop.identity";
});
services.AddMvc();
services.AddHealthChecks(checks =>


+ 4
- 2
src/Services/Ordering/Ordering.API/Application/Commands/IdentifierCommandHandler.cs View File

@ -45,9 +45,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
return CreateResultForDuplicateRequest();
}
else
{
var result = await _mediator.SendAsync(message.Command);
{
await _requestManager.CreateRequestForCommandAsync<T>(message.Id);
var result = await _mediator.SendAsync(message.Command);
return result;
}
}


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

@ -4,10 +4,10 @@
public interface IOrderQueries
{
Task<dynamic> GetOrder(int id);
Task<dynamic> GetOrderAsync(int id);
Task<dynamic> GetOrders();
Task<dynamic> GetOrdersAsync();
Task<dynamic> GetCardTypes();
Task<dynamic> GetCardTypesAsync();
}
}

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

@ -19,7 +19,7 @@
}
public async Task<dynamic> GetOrder(int id)
public async Task<dynamic> GetOrderAsync(int id)
{
using (var connection = new SqlConnection(_connectionString))
{
@ -44,7 +44,7 @@
}
}
public async Task<dynamic> GetOrders()
public async Task<dynamic> GetOrdersAsync()
{
using (var connection = new SqlConnection(_connectionString))
{
@ -58,7 +58,7 @@
}
}
public async Task<dynamic> GetCardTypes()
public async Task<dynamic> GetCardTypesAsync()
{
using (var connection = new SqlConnection(_connectionString))
{


+ 11
- 13
src/Services/Ordering/Ordering.API/Application/Validations/CreateOrderCommandValidator.cs View File

@ -1,10 +1,8 @@
using FluentValidation;
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand;
namespace Ordering.API.Application.Validations
@ -13,17 +11,17 @@ namespace Ordering.API.Application.Validations
{
public CreateOrderCommandValidator()
{
RuleFor(order => order.City).NotEmpty();
RuleFor(order => order.Street).NotEmpty();
RuleFor(order => order.State).NotEmpty();
RuleFor(order => order.Country).NotEmpty();
RuleFor(order => order.ZipCode).NotEmpty();
RuleFor(order => order.CardNumber).NotEmpty().Length(12, 19);
RuleFor(order => order.CardHolderName).NotEmpty();
RuleFor(order => order.CardExpiration).NotEmpty().Must(BeValidExpirationDate).WithMessage("Please specify a valid card expiration date");
RuleFor(order => order.CardSecurityNumber).NotEmpty().Length(3);
RuleFor(order => order.CardTypeId).NotEmpty();
RuleFor(order => order.OrderItems).Must(ContainOrderItems).WithMessage("No order items found");
RuleFor(command => command.City).NotEmpty();
RuleFor(command => command.Street).NotEmpty();
RuleFor(command => command.State).NotEmpty();
RuleFor(command => command.Country).NotEmpty();
RuleFor(command => command.ZipCode).NotEmpty();
RuleFor(command => command.CardNumber).NotEmpty().Length(12, 19);
RuleFor(command => command.CardHolderName).NotEmpty();
RuleFor(command => command.CardExpiration).NotEmpty().Must(BeValidExpirationDate).WithMessage("Please specify a valid card expiration date");
RuleFor(command => command.CardSecurityNumber).NotEmpty().Length(3);
RuleFor(command => command.CardTypeId).NotEmpty();
RuleFor(command => command.OrderItems).Must(ContainOrderItems).WithMessage("No order items found");
}
private bool BeValidExpirationDate(DateTime dateTime)


+ 1
- 5
src/Services/Ordering/Ordering.API/Application/Validations/IdentifierCommandValidator.cs View File

@ -1,9 +1,5 @@
using FluentValidation;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.API.Application.Validations
{
@ -11,7 +7,7 @@ namespace Ordering.API.Application.Validations
{
public IdentifierCommandValidator()
{
RuleFor(customer => customer.Id).NotEmpty();
RuleFor(command => command.Id).NotEmpty();
}
}
}

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

@ -57,7 +57,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
{
try
{
var order = await _orderQueries.GetOrder(orderId);
var order = await _orderQueries
.GetOrderAsync(orderId);
return Ok(order);
}
catch (KeyNotFoundException)
@ -70,7 +72,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[HttpGet]
public async Task<IActionResult> GetOrders()
{
var orders = await _orderQueries.GetOrders();
var orders = await _orderQueries
.GetOrdersAsync();
return Ok(orders);
}
@ -79,7 +82,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[HttpGet]
public async Task<IActionResult> GetCardTypes()
{
var cardTypes = await _orderQueries.GetCardTypes();
var cardTypes = await _orderQueries
.GetCardTypesAsync();
return Ok(cardTypes);
}


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

@ -41,12 +41,12 @@
{
var json = new JsonErrorResponse
{
Messages = new[] { "An error ocurr.Try it again." }
Messages = new[] { "An error occur.Try it again." }
};
if (env.IsDevelopment())
{
json.DeveloperMeesage = context.Exception;
json.DeveloperMessage = context.Exception;
}
// Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1
@ -61,7 +61,7 @@
{
public string[] Messages { get; set; }
public object DeveloperMeesage { get; set; }
public object DeveloperMessage { get; set; }
}
}
}

+ 24
- 9
src/Services/Ordering/Ordering.API/Startup.cs View File

@ -22,6 +22,7 @@
using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging;
using Ordering.Infrastructure;
using RabbitMQ.Client;
using System;
using System.Data.Common;
using System.Reflection;
@ -65,14 +66,14 @@
services.AddEntityFrameworkSqlServer()
.AddDbContext<OrderingContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
},
ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
options.UseSqlServer(Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
},
ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
);
services.AddSwaggerGen();
@ -105,7 +106,21 @@
sp => (DbConnection c) => new IntegrationEventLogService(c));
var serviceProvider = services.BuildServiceProvider();
services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>();
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(Configuration["EventBusConnection"]));
services.AddSingleton<IRabbitMQPersisterConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersisterConnection>>();
var factory = new ConnectionFactory()
{
HostName = Configuration["EventBusConnection"]
};
return new DefaultRabbitMQPersisterConnection(factory, logger);
});
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddOptions();
//configure autofac


+ 4
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs View File

@ -16,6 +16,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
protected Buyer() {
_paymentMethods = new List<PaymentMethod>();
}
@ -34,6 +35,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
if (existingPayment != null)
{
AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, existingPayment, orderId));
return existingPayment;
}
else
@ -41,7 +43,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration);
_paymentMethods.Add(payment);
AddDomainEvent(new BuyerAndPaymentMethodVerifiedDomainEvent(this, payment, orderId));
return payment;
}
}


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

@ -9,9 +9,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public interface IOrderRepository : IRepository<Order>
{
Order Add(Order order);
void Update(Order order);
Task<Order> GetAsync(int orderId);
void Update(Order order);
}
}

+ 3
- 3
src/Services/Ordering/Ordering.Domain/SeedWork/Enumeration.cs View File

@ -71,17 +71,17 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork
public static T FromValue<T>(int value) where T : Enumeration, new()
{
var matchingItem = parse<T, int>(value, "value", item => item.Id == value);
var matchingItem = Parse<T, int>(value, "value", item => item.Id == value);
return matchingItem;
}
public static T FromDisplayName<T>(string displayName) where T : Enumeration, new()
{
var matchingItem = parse<T, string>(displayName, "display name", item => item.Name == displayName);
var matchingItem = Parse<T, string>(displayName, "display name", item => item.Name == displayName);
return matchingItem;
}
private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new()
private static T Parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new()
{
var matchingItem = GetAll<T>().FirstOrDefault(predicate);


src/Services/Ordering/Ordering.Infrastructure/ClientRequest.cs → src/Services/Ordering/Ordering.Infrastructure/Idempotency/ClientRequest.cs View File

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency
{
public class ClientRequest
{

+ 1
- 2
src/Services/Ordering/Ordering.Infrastructure/Idempotency/IRequestManager.cs View File

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency
@ -8,6 +6,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempoten
public interface IRequestManager
{
Task<bool> ExistAsync(Guid id);
Task CreateRequestForCommandAsync<T>(Guid id);
}
}

+ 10
- 10
src/Services/Ordering/Ordering.Infrastructure/Idempotency/RequestManager.cs View File

@ -1,8 +1,5 @@
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using Ordering.Domain.Exceptions;
using Ordering.Domain.Exceptions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency
@ -10,22 +7,25 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempoten
public class RequestManager : IRequestManager
{
private readonly OrderingContext _context;
public RequestManager(OrderingContext ctx)
public RequestManager(OrderingContext context)
{
_context = ctx;
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task<bool> ExistAsync(Guid id)
{
var request = await _context.FindAsync<ClientRequest>(id);
var request = await _context.
FindAsync<ClientRequest>(id);
return request != null;
}
public async Task CreateRequestForCommandAsync<T>(Guid id)
{
{
var exists = await ExistAsync(id);
var request = exists ?
throw new OrderingDomainException($"Request with {id} already exists") :
new ClientRequest()
@ -36,8 +36,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempoten
};
_context.Add(request);
await _context.SaveChangesAsync();
}
}
}

+ 11
- 4
src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs View File

@ -6,13 +6,20 @@ using System.Threading.Tasks;
namespace Ordering.Infrastructure
{
public static class MediatorExtension
static class MediatorExtension
{
public static async Task DispatchDomainEventsAsync(this IMediator mediator, OrderingContext ctx)
{
var domainEntities = ctx.ChangeTracker.Entries<Entity>().Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());
var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList();
domainEntities.ToList().ForEach(entity => entity.Entity.DomainEvents.Clear());
var domainEntities = ctx.ChangeTracker
.Entries<Entity>()
.Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());
var domainEvents = domainEntities
.SelectMany(x => x.Entity.DomainEvents)
.ToList();
domainEntities.ToList()
.ForEach(entity => entity.Entity.DomainEvents.Clear());
var tasks = domainEvents
.Select(async (domainEvent) => {


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

@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using Ordering.Infrastructure;
using System;
using System.Threading;
@ -34,7 +35,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
public OrderingContext(DbContextOptions options, IMediator mediator) : base(options)
{
_mediator = mediator;
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)


+ 5
- 0
src/Web/WebMVC/Startup.cs View File

@ -44,6 +44,11 @@ namespace Microsoft.eShopOnContainers.WebMVC
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection(opts =>
{
opts.ApplicationDiscriminator = "eshop.webmvc";
});
services.AddMvc();
services.Configure<AppSettings>(Configuration);


+ 1
- 1
src/Web/WebSPA/Client/modules/app.module.ts View File

@ -2,7 +2,7 @@ import { NgModule, NgModuleFactoryLoader } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
// import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/Router';
import { RouterModule } from '@angular/router';
import { routing } from './app.routes';
import { AppService } from './app.service';


+ 5
- 0
src/Web/WebSPA/Startup.cs View File

@ -51,6 +51,11 @@ namespace eShopConContainers.WebSPA
services.Configure<AppSettings>(Configuration);
services.AddDataProtection(opts =>
{
opts.ApplicationDiscriminator = "eshop.webspa";
});
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
services.AddMvc()


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

@ -60,7 +60,7 @@ namespace UnitTest.Ordering.Application
{
//Arrange
var fakeDynamicResult = new Object();
_orderQueriesMock.Setup(x => x.GetOrders())
_orderQueriesMock.Setup(x => x.GetOrdersAsync())
.Returns(Task.FromResult(fakeDynamicResult));
//Act
@ -77,7 +77,7 @@ namespace UnitTest.Ordering.Application
//Arrange
var fakeOrderId = 123;
var fakeDynamicResult = new Object();
_orderQueriesMock.Setup(x => x.GetOrder(It.IsAny<int>()))
_orderQueriesMock.Setup(x => x.GetOrderAsync(It.IsAny<int>()))
.Returns(Task.FromResult(fakeDynamicResult));
//Act
@ -93,7 +93,7 @@ namespace UnitTest.Ordering.Application
{
//Arrange
var fakeDynamicResult = new Object();
_orderQueriesMock.Setup(x => x.GetCardTypes())
_orderQueriesMock.Setup(x => x.GetCardTypesAsync())
.Returns(Task.FromResult(fakeDynamicResult));
//Act


Loading…
Cancel
Save