Merge branch 'reviews/uzorrilla' into dev

# Conflicts:
#	src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs
This commit is contained in:
Eduard Tomas 2017-04-18 15:34:31 +02:00
commit 658904ce5c
43 changed files with 217 additions and 242 deletions

View File

@ -39,24 +39,10 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ordering.Infrastructure", "src\Services\Ordering\Ordering.Infrastructure\Ordering.Infrastructure.csproj", "{95F1F07C-4D92-4742-BD07-E5B805AAB651}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ordering.Infrastructure", "src\Services\Ordering\Ordering.Infrastructure\Ordering.Infrastructure.csproj", "{95F1F07C-4D92-4742-BD07-E5B805AAB651}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "test\Services\UnitTest\UnitTest.csproj", "{7796F5D8-31FC-45A4-B673-19DE5BA194CF}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "test\Services\UnitTest\UnitTest.csproj", "{7796F5D8-31FC-45A4-B673-19DE5BA194CF}"
ProjectSection(ProjectDependencies) = postProject
{A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B}
{F16E3C6A-1C94-4EAB-BE91-099618060B68} = {F16E3C6A-1C94-4EAB-BE91-099618060B68}
EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity.API", "src\Services\Identity\Identity.API\Identity.API.csproj", "{A579E108-5445-403D-A407-339AC4D1611B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity.API", "src\Services\Identity\Identity.API\Identity.API.csproj", "{A579E108-5445-403D-A407-339AC4D1611B}"
EndProject EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}" Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}"
ProjectSection(ProjectDependencies) = postProject
{A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B}
{5B810E3D-112E-4857-B197-F09D2FD41E27} = {5B810E3D-112E-4857-B197-F09D2FD41E27}
{F16E3C6A-1C94-4EAB-BE91-099618060B68} = {F16E3C6A-1C94-4EAB-BE91-099618060B68}
{F0333D8E-0B27-42B7-B2C6-78F3657624E2} = {F0333D8E-0B27-42B7-B2C6-78F3657624E2}
{42681D9D-750A-4DF7-BD9F-9292CFD5C253} = {42681D9D-750A-4DF7-BD9F-9292CFD5C253}
{2110CBB0-3B38-4EE4-A743-DF6968D80D90} = {2110CBB0-3B38-4EE4-A743-DF6968D80D90}
{CFE2FACB-4538-4B99-8A10-306F3882952D} = {CFE2FACB-4538-4B99-8A10-306F3882952D}
{231226CE-690B-4979-8870-9A79D80928E2} = {231226CE-690B-4979-8870-9A79D80928E2}
EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSPA", "src\Web\WebSPA\WebSPA.csproj", "{F16E3C6A-1C94-4EAB-BE91-099618060B68}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSPA", "src\Web\WebSPA\WebSPA.csproj", "{F16E3C6A-1C94-4EAB-BE91-099618060B68}"
EndProject EndProject

View File

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

View File

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

View File

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

View File

@ -5,7 +5,6 @@ using Newtonsoft.Json;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -35,7 +34,9 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
public void Publish(IntegrationEvent @event) public void Publish(IntegrationEvent @event)
{ {
var eventName = @event.GetType().Name; var eventName = @event.GetType()
.Name;
var factory = new ConnectionFactory() { HostName = _connectionString }; var factory = new ConnectionFactory() { HostName = _connectionString };
using (var connection = factory.CreateConnection()) using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel()) using (var channel = connection.CreateModel())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,11 @@
using System; using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using StackExchange.Redis;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.Extensions.Logging; using StackExchange.Redis;
using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
{ {
@ -31,7 +30,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
return await database.KeyDeleteAsync(id.ToString()); return await database.KeyDeleteAsync(id.ToString());
} }
public async Task<IEnumerable<string>> GetUsers() public async Task<IEnumerable<string>> GetUsersAsync()
{ {
var server = await GetServer(); 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)); var created = await database.StringSetAsync(basket.BuyerId, JsonConvert.SerializeObject(basket));
if (!created) if (!created)
{ {
_logger.LogInformation("Problem persisting the item"); _logger.LogInformation("Problem occur persisting the item.");
return null; return null;
} }
_logger.LogInformation("basket item persisted succesfully"); _logger.LogInformation("Basket item persisted succesfully.");
return await GetBasketAsync(basket.BuyerId); return await GetBasketAsync(basket.BuyerId);
} }

View File

@ -1,24 +1,23 @@
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.Builder;
using Microsoft.AspNetCore.Hosting; 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.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using StackExchange.Redis;
using Microsoft.Extensions.Options;
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 Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System.Linq;
using System.Net;
using System.Threading.Tasks; 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 namespace Microsoft.eShopOnContainers.Services.Basket.API
{ {
@ -59,17 +58,24 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
//and then creating the connection it seems reasonable to move //and then creating the connection it seems reasonable to move
//that cost to startup instead of having the first request pay the //that cost to startup instead of having the first request pay the
//penalty. //penalty.
services.AddSingleton<ConnectionMultiplexer>((sp) => { services.AddSingleton<ConnectionMultiplexer>(sp => {
var config = sp.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value; var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
var ips = Dns.GetHostAddressesAsync(config.ConnectionString).Result; var ips = Dns.GetHostAddressesAsync(settings.ConnectionString).Result;
return ConnectionMultiplexer.Connect(ips.First().ToString()); return ConnectionMultiplexer.Connect(ips.First().ToString());
}); });
services.AddSingleton<IEventBus>(sp =>
{
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
return new EventBusRabbitMQ(settings.EventBusConnection);
});
services.AddSwaggerGen(); services.AddSwaggerGen();
//var sch = new IdentitySecurityScheme();
services.ConfigureSwaggerGen(options => services.ConfigureSwaggerGen(options =>
{ {
//options.AddSecurityDefinition("IdentityServer", sch);
options.OperationFilter<AuthorizationHeaderParameterOperationFilter>(); options.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
options.DescribeAllEnumsAsStrings(); options.DescribeAllEnumsAsStrings();
options.SingleApiVersion(new Swashbuckle.Swagger.Model.Info() options.SingleApiVersion(new Swashbuckle.Swagger.Model.Info()
@ -95,10 +101,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>(); services.AddTransient<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>, ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<IIntegrationEventHandler<OrderStartedIntegrationEvent>, OrderStartedIntegrationEventHandler>(); services.AddTransient<IIntegrationEventHandler<OrderStartedIntegrationEvent>, OrderStartedIntegrationEventHandler>();
var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
services.AddSingleton<IEventBus>(provider => new EventBusRabbitMQ(configuration.EventBusConnection));
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -119,11 +122,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
app.UseSwagger() app.UseSwagger()
.UseSwaggerUi(); .UseSwaggerUi();
var catalogPriceHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>>(); ConfigureEventBus(app);
var orderStartedHandler = app.ApplicationServices.GetService<IIntegrationEventHandler<OrderStartedIntegrationEvent>>();
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent>(catalogPriceHandler);
eventBus.Subscribe<OrderStartedIntegrationEvent>(orderStartedHandler);
} }
@ -136,6 +135,21 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
ScopeName = "basket", ScopeName = "basket",
RequireHttpsMetadata = false 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);
}
} }
} }

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; }
}
}

View File

@ -1,9 +1,6 @@
using Microsoft.AspNetCore.Mvc; using Catalog.API.IntegrationEvents;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; 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.Infrastructure;
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Catalog.API.Model; using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
@ -11,12 +8,8 @@ using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.Common;
using System.Linq; using System.Linq;
using System.Threading.Tasks; 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 namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{ {
@ -24,16 +17,16 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
public class CatalogController : ControllerBase public class CatalogController : ControllerBase
{ {
private readonly CatalogContext _catalogContext; private readonly CatalogContext _catalogContext;
private readonly IOptionsSnapshot<Settings> _settings; private readonly CatalogSettings _settings;
private readonly ICatalogIntegrationEventService _catalogIntegrationEventService; private readonly ICatalogIntegrationEventService _catalogIntegrationEventService;
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, ICatalogIntegrationEventService catalogIntegrationEventService) public CatalogController(CatalogContext context, IOptionsSnapshot<CatalogSettings> settings, ICatalogIntegrationEventService catalogIntegrationEventService)
{ {
_catalogContext = Context; _catalogContext = context ?? throw new ArgumentNullException(nameof(context));
_catalogIntegrationEventService = catalogIntegrationEventService; _catalogIntegrationEventService = catalogIntegrationEventService ?? throw new ArgumentNullException(nameof(catalogIntegrationEventService));
_settings = settings;
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; _settings = settings.Value;
((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
} }
// GET api/v1/[controller]/items[?pageSize=3&pageIndex=10] // GET api/v1/[controller]/items[?pageSize=3&pageIndex=10]
@ -51,7 +44,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
.Take(pageSize) .Take(pageSize)
.ToListAsync(); .ToListAsync();
itemsOnPage = ComposePicUri(itemsOnPage); itemsOnPage = ChangeUriPlaceholder(itemsOnPage);
var model = new PaginatedItemsViewModel<CatalogItem>( var model = new PaginatedItemsViewModel<CatalogItem>(
pageIndex, pageSize, totalItems, itemsOnPage); pageIndex, pageSize, totalItems, itemsOnPage);
@ -75,7 +68,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
.Take(pageSize) .Take(pageSize)
.ToListAsync(); .ToListAsync();
itemsOnPage = ComposePicUri(itemsOnPage); itemsOnPage = ChangeUriPlaceholder(itemsOnPage);
var model = new PaginatedItemsViewModel<CatalogItem>( var model = new PaginatedItemsViewModel<CatalogItem>(
pageIndex, pageSize, totalItems, itemsOnPage); pageIndex, pageSize, totalItems, itemsOnPage);
@ -108,7 +101,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
.Take(pageSize) .Take(pageSize)
.ToListAsync(); .ToListAsync();
itemsOnPage = ComposePicUri(itemsOnPage); itemsOnPage = ChangeUriPlaceholder(itemsOnPage);
var model = new PaginatedItemsViewModel<CatalogItem>( var model = new PaginatedItemsViewModel<CatalogItem>(
pageIndex, pageSize, totalItems, itemsOnPage); pageIndex, pageSize, totalItems, itemsOnPage);
@ -143,10 +136,17 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
[HttpPost] [HttpPost]
public async Task<IActionResult> UpdateProduct([FromBody]CatalogItem productToUpdate) public async Task<IActionResult> UpdateProduct([FromBody]CatalogItem productToUpdate)
{ {
var catalogItem = await _catalogContext.CatalogItems.SingleOrDefaultAsync(i => i.Id == productToUpdate.Id); var catalogItem = await _catalogContext.CatalogItems
if (catalogItem == null) return NotFound(); .SingleOrDefaultAsync(i => i.Id == productToUpdate.Id);
var raiseProductPriceChangedEvent = catalogItem.Price != productToUpdate.Price;
if (catalogItem == null)
{
return NotFound();
}
var oldPrice = catalogItem.Price; var oldPrice = catalogItem.Price;
var raiseProductPriceChangedEvent = oldPrice != productToUpdate.Price;
// Update current product // Update current product
catalogItem = productToUpdate; catalogItem = productToUpdate;
@ -205,13 +205,16 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
} }
_catalogContext.CatalogItems.Remove(product); _catalogContext.CatalogItems.Remove(product);
await _catalogContext.SaveChangesAsync(); await _catalogContext.SaveChangesAsync();
return Ok(); return Ok();
} }
private List<CatalogItem> ComposePicUri(List<CatalogItem> items) { private List<CatalogItem> ChangeUriPlaceholder(List<CatalogItem> items)
var baseUri = _settings.Value.ExternalCatalogBaseUrl; {
var baseUri = _settings.ExternalCatalogBaseUrl;
items.ForEach(x => items.ForEach(x =>
{ {
x.PictureUri = x.PictureUri.Replace("http://externalcatalogbaseurltobereplaced", baseUri); x.PictureUri = x.PictureUri.Replace("http://externalcatalogbaseurltobereplaced", baseUri);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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; }
}
}

View File

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

View File

@ -4,10 +4,10 @@
public interface IOrderQueries 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();
} }
} }

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)) 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)) 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)) using (var connection = new SqlConnection(_connectionString))
{ {

View File

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

View File

@ -1,9 +1,5 @@
using FluentValidation; using FluentValidation;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; 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 namespace Ordering.API.Application.Validations
{ {
@ -11,7 +7,7 @@ namespace Ordering.API.Application.Validations
{ {
public IdentifierCommandValidator() public IdentifierCommandValidator()
{ {
RuleFor(customer => customer.Id).NotEmpty(); RuleFor(command => command.Id).NotEmpty();
} }
} }
} }

View File

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

View File

@ -65,14 +65,14 @@
services.AddEntityFrameworkSqlServer() services.AddEntityFrameworkSqlServer()
.AddDbContext<OrderingContext>(options => .AddDbContext<OrderingContext>(options =>
{ {
options.UseSqlServer(Configuration["ConnectionString"], options.UseSqlServer(Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions => sqlServerOptionsAction: sqlOptions =>
{ {
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); 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) ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
); );
services.AddSwaggerGen(); services.AddSwaggerGen();

View File

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

View File

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

View File

@ -71,17 +71,17 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork
public static T FromValue<T>(int value) where T : Enumeration, new() 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; return matchingItem;
} }
public static T FromDisplayName<T>(string displayName) where T : Enumeration, new() 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; 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); var matchingItem = GetAll<T>().FirstOrDefault(predicate);

View File

@ -1,8 +1,6 @@
using System; 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 public class ClientRequest
{ {

View File

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

View File

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

View File

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

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

View File

@ -13,6 +13,8 @@
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath> <DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
<TypeScriptCompileOnSaveEnabled>false</TypeScriptCompileOnSaveEnabled> <TypeScriptCompileOnSaveEnabled>false</TypeScriptCompileOnSaveEnabled>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<GeneratedItemPatterns>wwwroot/dist/**</GeneratedItemPatterns>
<DefaultItemExcludes>$(DefaultItemExcludes);$(GeneratedItemPatterns)</DefaultItemExcludes>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -67,9 +69,14 @@
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.0" /> <PackageReference Include="Microsoft.Extensions.Options" Version="1.1.0" />
</ItemGroup> </ItemGroup>
<!-- workaround for https://github.com/aspnet/websdk/issues/114 -->
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish"> <Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
<Exec Command="npm install" /> <Exec Command="npm install" />
<Exec Command="npm run build:prod" /> <Exec Command="npm run build:prod" />
<ItemGroup>
<_GeneratedFiles Include="$(GeneratedItemPatterns)" />
<ContentWithTargetPath Include="@(_GeneratedFiles)" TargetPath="%(Identity)" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
</Target> </Target>
<ItemGroup> <ItemGroup>

View File

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