Merge branch 'integration-events-rmq' into dev
This commit is contained in:
commit
f03cf0e8f1
@ -14,6 +14,7 @@ services:
|
|||||||
- ASPNETCORE_URLS=http://0.0.0.0:5103
|
- ASPNETCORE_URLS=http://0.0.0.0:5103
|
||||||
- ConnectionString=basket.data
|
- ConnectionString=basket.data
|
||||||
- identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
- identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
|
- EventBusConnection=rabbitmq
|
||||||
ports:
|
ports:
|
||||||
- "5103:5103"
|
- "5103:5103"
|
||||||
|
|
||||||
@ -22,7 +23,8 @@ services:
|
|||||||
- ASPNETCORE_ENVIRONMENT=Development
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
- ASPNETCORE_URLS=http://0.0.0.0:5101
|
- ASPNETCORE_URLS=http://0.0.0.0:5101
|
||||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word
|
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word
|
||||||
- ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5101 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
- ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5101 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
|
- EventBusConnection=rabbitmq
|
||||||
ports:
|
ports:
|
||||||
- "5101:5101"
|
- "5101:5101"
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ services:
|
|||||||
- ASPNETCORE_URLS=http://0.0.0.0:5102
|
- ASPNETCORE_URLS=http://0.0.0.0:5102
|
||||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
|
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
|
||||||
- identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
- identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
|
- EventBusConnection=rabbitmq
|
||||||
ports:
|
ports:
|
||||||
- "5102:5102"
|
- "5102:5102"
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- basket.data
|
- basket.data
|
||||||
- identity.api
|
- identity.api
|
||||||
|
- rabbitmq
|
||||||
|
|
||||||
catalog.api:
|
catalog.api:
|
||||||
image: eshop/catalog.api
|
image: eshop/catalog.api
|
||||||
@ -17,6 +18,7 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- sql.data
|
- sql.data
|
||||||
|
- rabbitmq
|
||||||
|
|
||||||
identity.api:
|
identity.api:
|
||||||
image: eshop/identity.api
|
image: eshop/identity.api
|
||||||
@ -61,3 +63,8 @@ services:
|
|||||||
image: redis
|
image: redis
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
image: rabbitmq
|
||||||
|
ports:
|
||||||
|
- "5672:5672"
|
||||||
|
@ -50,6 +50,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTests", "test\Se
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Services\FunctionalTests\FunctionalTests.csproj", "{CFE2FACB-4538-4B99-8A10-306F3882952D}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Services\FunctionalTests\FunctionalTests.csproj", "{CFE2FACB-4538-4B99-8A10-306F3882952D}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{47857844-D05A-4C37-BFB2-AF19B7EC418D}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Services\Common\Infrastructure\Infrastructure.csproj", "{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{9C2566F6-3A98-4B1D-A6B2-35820CE88B73}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{7B41F409-D86D-4697-99BC-F8EBA21BB18F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||||
@ -544,6 +552,54 @@ Global
|
|||||||
{CFE2FACB-4538-4B99-8A10-306F3882952D}.Release|x64.Build.0 = Release|Any CPU
|
{CFE2FACB-4538-4B99-8A10-306F3882952D}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{CFE2FACB-4538-4B99-8A10-306F3882952D}.Release|x86.ActiveCfg = Release|Any CPU
|
{CFE2FACB-4538-4B99-8A10-306F3882952D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{CFE2FACB-4538-4B99-8A10-306F3882952D}.Release|x86.Build.0 = Release|Any CPU
|
{CFE2FACB-4538-4B99-8A10-306F3882952D}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -567,5 +623,8 @@ Global
|
|||||||
{F16E3C6A-1C94-4EAB-BE91-099618060B68} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
|
{F16E3C6A-1C94-4EAB-BE91-099618060B68} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
|
||||||
{5B810E3D-112E-4857-B197-F09D2FD41E27} = {EF0337F2-ED00-4643-89FD-EE10863F1870}
|
{5B810E3D-112E-4857-B197-F09D2FD41E27} = {EF0337F2-ED00-4643-89FD-EE10863F1870}
|
||||||
{CFE2FACB-4538-4B99-8A10-306F3882952D} = {EF0337F2-ED00-4643-89FD-EE10863F1870}
|
{CFE2FACB-4538-4B99-8A10-306F3882952D} = {EF0337F2-ED00-4643-89FD-EE10863F1870}
|
||||||
|
{B08BA891-ABA2-4BD5-80E8-40B7546C3BE0} = {7B41F409-D86D-4697-99BC-F8EBA21BB18F}
|
||||||
|
{9C2566F6-3A98-4B1D-A6B2-35820CE88B73} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
|
||||||
|
{7B41F409-D86D-4697-99BC-F8EBA21BB18F} = {9C2566F6-3A98-4B1D-A6B2-35820CE88B73}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
@ -39,6 +39,10 @@
|
|||||||
<PackageReference Include="Swashbuckle" Version="6.0.0-beta902" />
|
<PackageReference Include="Swashbuckle" Version="6.0.0-beta902" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Common\Infrastructure\Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="Dockerfile">
|
<None Update="Dockerfile">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
@ -8,5 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
|||||||
public class BasketSettings
|
public class BasketSettings
|
||||||
{
|
{
|
||||||
public string ConnectionString { get; set; }
|
public string ConnectionString { get; set; }
|
||||||
|
|
||||||
|
public string EventBusConnection { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Basket.API.Events
|
||||||
|
{
|
||||||
|
public class ProductPriceChangedHandler : IIntegrationEventHandler<ProductPriceChanged>
|
||||||
|
{
|
||||||
|
private readonly IBasketRepository _repository;
|
||||||
|
public ProductPriceChangedHandler(IBasketRepository repository)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(ProductPriceChanged @event)
|
||||||
|
{
|
||||||
|
var userIds = await _repository.GetUsers();
|
||||||
|
foreach (var id in userIds)
|
||||||
|
{
|
||||||
|
var basket = await _repository.GetBasket(id);
|
||||||
|
await UpdateBasket(@event.ItemId, @event.NewPrice, basket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateBasket(int itemId, decimal newPrice, CustomerBasket basket)
|
||||||
|
{
|
||||||
|
var itemsToUpdate = basket?.Items?.Where(x => int.Parse(x.ProductId) == itemId).ToList();
|
||||||
|
if (itemsToUpdate != null)
|
||||||
|
{
|
||||||
|
foreach (var item in itemsToUpdate)
|
||||||
|
{
|
||||||
|
var originalPrice = item.UnitPrice;
|
||||||
|
item.UnitPrice = newPrice;
|
||||||
|
item.OldUnitPrice = originalPrice;
|
||||||
|
}
|
||||||
|
await _repository.UpdateBasket(basket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
|
|||||||
public string ProductId { get; set; }
|
public string ProductId { get; set; }
|
||||||
public string ProductName { get; set; }
|
public string ProductName { get; set; }
|
||||||
public decimal UnitPrice { get; set; }
|
public decimal UnitPrice { get; set; }
|
||||||
|
public decimal OldUnitPrice { get; set; }
|
||||||
public int Quantity { get; set; }
|
public int Quantity { get; set; }
|
||||||
public string PictureUrl { get; set; }
|
public string PictureUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
|
|||||||
public interface IBasketRepository
|
public interface IBasketRepository
|
||||||
{
|
{
|
||||||
Task<CustomerBasket> GetBasket(string customerId);
|
Task<CustomerBasket> GetBasket(string customerId);
|
||||||
|
Task<IEnumerable<string>> GetUsers();
|
||||||
Task<CustomerBasket> UpdateBasket(CustomerBasket basket);
|
Task<CustomerBasket> UpdateBasket(CustomerBasket basket);
|
||||||
Task<bool> DeleteBasket(string id);
|
Task<bool> DeleteBasket(string id);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,18 @@ 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()
|
||||||
|
{
|
||||||
|
var server = await GetServer();
|
||||||
|
|
||||||
|
IEnumerable<RedisKey> data = server.Keys();
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return data.Select(k => k.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<CustomerBasket> GetBasket(string customerId)
|
public async Task<CustomerBasket> GetBasket(string customerId)
|
||||||
{
|
{
|
||||||
var database = await GetDatabase();
|
var database = await GetDatabase();
|
||||||
@ -63,14 +75,32 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
|
|||||||
{
|
{
|
||||||
if (_redis == null)
|
if (_redis == null)
|
||||||
{
|
{
|
||||||
//TODO: Need to make this more robust. Also want to understand why the static connection method cannot accept dns names.
|
await ConnectToRedisAsync();
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _redis.GetDatabase();
|
return _redis.GetDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<IServer> GetServer()
|
||||||
|
{
|
||||||
|
if (_redis == null)
|
||||||
|
{
|
||||||
|
await ConnectToRedisAsync();
|
||||||
|
}
|
||||||
|
var endpoint = _redis.GetEndPoints();
|
||||||
|
|
||||||
|
return _redis.GetServer(endpoint.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,9 @@ using Microsoft.Extensions.Options;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Swashbuckle.Swagger.Model;
|
using Swashbuckle.Swagger.Model;
|
||||||
using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
|
using Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog;
|
||||||
|
using Basket.API.Events;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Basket.API
|
namespace Microsoft.eShopOnContainers.Services.Basket.API
|
||||||
{
|
{
|
||||||
@ -76,6 +79,17 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
|
|||||||
});
|
});
|
||||||
|
|
||||||
services.AddTransient<IBasketRepository, RedisBasketRepository>();
|
services.AddTransient<IBasketRepository, RedisBasketRepository>();
|
||||||
|
services.AddTransient<IIntegrationEventHandler<ProductPriceChanged>, ProductPriceChangedHandler>();
|
||||||
|
|
||||||
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
|
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
|
||||||
|
var eventBus = new EventBusRabbitMQ(configuration.EventBusConnection);
|
||||||
|
services.AddSingleton<IEventBus>(eventBus);
|
||||||
|
|
||||||
|
|
||||||
|
var catalogPriceHandler = serviceProvider.GetService<IIntegrationEventHandler<ProductPriceChanged>>();
|
||||||
|
eventBus.Subscribe<ProductPriceChanged>(catalogPriceHandler);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||||
<PackageReference Include="Swashbuckle" Version="6.0.0-beta902" />
|
<PackageReference Include="Swashbuckle" Version="6.0.0-beta902" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@ -52,6 +53,10 @@
|
|||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Common\Infrastructure\Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="Dockerfile">
|
<None Update="Dockerfile">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
@ -3,7 +3,11 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
|
||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -15,11 +19,13 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
{
|
{
|
||||||
private readonly CatalogContext _context;
|
private readonly CatalogContext _context;
|
||||||
private readonly IOptionsSnapshot<Settings> _settings;
|
private readonly IOptionsSnapshot<Settings> _settings;
|
||||||
|
private readonly IEventBus _eventBus;
|
||||||
|
|
||||||
public CatalogController(CatalogContext context, IOptionsSnapshot<Settings> settings)
|
public CatalogController(CatalogContext context, IOptionsSnapshot<Settings> settings, IEventBus eventBus)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
|
_eventBus = eventBus;
|
||||||
|
|
||||||
((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||||
}
|
}
|
||||||
@ -41,7 +47,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
itemsOnPage = ComposePicUri(itemsOnPage);
|
itemsOnPage = ComposePicUri(itemsOnPage);
|
||||||
|
|
||||||
var model = new PaginatedItemsViewModel<CatalogItem>(
|
var model = new PaginatedItemsViewModel<CatalogItem>(
|
||||||
pageIndex, pageSize, totalItems, itemsOnPage);
|
pageIndex, pageSize, totalItems, itemsOnPage);
|
||||||
|
|
||||||
return Ok(model);
|
return Ok(model);
|
||||||
}
|
}
|
||||||
@ -99,7 +105,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
|
|
||||||
var model = new PaginatedItemsViewModel<CatalogItem>(
|
var model = new PaginatedItemsViewModel<CatalogItem>(
|
||||||
pageIndex, pageSize, totalItems, itemsOnPage);
|
pageIndex, pageSize, totalItems, itemsOnPage);
|
||||||
|
|
||||||
return Ok(model);
|
return Ok(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,6 +131,31 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
return Ok(items);
|
return Ok(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Post([FromBody]CatalogItem value)
|
||||||
|
{
|
||||||
|
var item = await _context.CatalogItems.SingleOrDefaultAsync(i => i.Id == value.Id);
|
||||||
|
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Price != value.Price)
|
||||||
|
{
|
||||||
|
var oldPrice = item.Price;
|
||||||
|
item.Price = value.Price;
|
||||||
|
|
||||||
|
_context.CatalogItems.Update(item);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var @event = new ProductPriceChanged(item.Id, item.Price, oldPrice);
|
||||||
|
await ProcessEventAsync(@event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
private List<CatalogItem> ComposePicUri(List<CatalogItem> items) {
|
private List<CatalogItem> ComposePicUri(List<CatalogItem> items) {
|
||||||
var baseUri = _settings.Value.ExternalCatalogBaseUrl;
|
var baseUri = _settings.Value.ExternalCatalogBaseUrl;
|
||||||
items.ForEach(x =>
|
items.ForEach(x =>
|
||||||
@ -134,5 +165,22 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ProcessEventAsync(IntegrationEventBase @event)
|
||||||
|
{
|
||||||
|
_eventBus.Publish(@event);
|
||||||
|
var eventData = new IntegrationEvent(@event);
|
||||||
|
eventData.TimesSent++;
|
||||||
|
eventData.State = EventStateEnum.Sent;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_context.IntegrationEvents.Add(eventData);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var t = ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
using EntityFrameworkCore.Metadata.Builders;
|
using EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Model;
|
using Model;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data;
|
||||||
|
|
||||||
public class CatalogContext : DbContext
|
public class CatalogContext : DbContext
|
||||||
{
|
{
|
||||||
@ -12,12 +13,15 @@
|
|||||||
public DbSet<CatalogItem> CatalogItems { get; set; }
|
public DbSet<CatalogItem> CatalogItems { get; set; }
|
||||||
public DbSet<CatalogBrand> CatalogBrands { get; set; }
|
public DbSet<CatalogBrand> CatalogBrands { get; set; }
|
||||||
public DbSet<CatalogType> CatalogTypes { get; set; }
|
public DbSet<CatalogType> CatalogTypes { get; set; }
|
||||||
|
public DbSet<IntegrationEvent> IntegrationEvents { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
|
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
|
||||||
builder.Entity<CatalogType>(ConfigureCatalogType);
|
builder.Entity<CatalogType>(ConfigureCatalogType);
|
||||||
builder.Entity<CatalogItem>(ConfigureCatalogItem);
|
builder.Entity<CatalogItem>(ConfigureCatalogItem);
|
||||||
}
|
builder.Entity<IntegrationEvent>(ConfigureIntegrationEvent);
|
||||||
|
}
|
||||||
|
|
||||||
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
|
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
|
||||||
{
|
{
|
||||||
@ -75,5 +79,31 @@
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100);
|
.HasMaxLength(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigureIntegrationEvent(EntityTypeBuilder<IntegrationEvent> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("IntegrationEvent");
|
||||||
|
|
||||||
|
builder.HasKey(e => e.EventId);
|
||||||
|
|
||||||
|
builder.Property(e => e.EventId)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(e => e.Content)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(e => e.CreationTime)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(e => e.State)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(e => e.TimesSent)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(e => e.EventTypeName)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
123
src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs
generated
Normal file
123
src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs
generated
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
|
||||||
|
|
||||||
|
namespace Catalog.API.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CatalogContext))]
|
||||||
|
[Migration("20170314083211_AddEventTable")]
|
||||||
|
partial class AddEventTable
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_brand_hilo")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<string>("Brand")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("CatalogBrand");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<int>("CatalogBrandId");
|
||||||
|
|
||||||
|
b.Property<int>("CatalogTypeId");
|
||||||
|
|
||||||
|
b.Property<string>("Description");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50);
|
||||||
|
|
||||||
|
b.Property<string>("PictureUri");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CatalogBrandId");
|
||||||
|
|
||||||
|
b.HasIndex("CatalogTypeId");
|
||||||
|
|
||||||
|
b.ToTable("Catalog");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_type_hilo")
|
||||||
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("CatalogType");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEvent", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("EventId")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime");
|
||||||
|
|
||||||
|
b.Property<string>("EventTypeName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.Property<int>("State");
|
||||||
|
|
||||||
|
b.Property<int>("TimesSent");
|
||||||
|
|
||||||
|
b.HasKey("EventId");
|
||||||
|
|
||||||
|
b.ToTable("IntegrationEvent");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CatalogBrandId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CatalogTypeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Catalog.API.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddEventTable : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "IntegrationEvent",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
EventId = table.Column<Guid>(nullable: false),
|
||||||
|
Content = table.Column<string>(nullable: false),
|
||||||
|
CreationTime = table.Column<DateTime>(nullable: false),
|
||||||
|
EventTypeName = table.Column<string>(maxLength: 200, nullable: false),
|
||||||
|
State = table.Column<int>(nullable: false),
|
||||||
|
TimesSent = table.Column<int>(nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_IntegrationEvent", x => x.EventId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "IntegrationEvent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
|||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
|
||||||
|
|
||||||
namespace Catalog.API.Infrastructure.Migrations
|
namespace Catalog.API.Infrastructure.Migrations
|
||||||
{
|
{
|
||||||
@ -13,13 +14,13 @@ namespace Catalog.API.Infrastructure.Migrations
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "1.0.1")
|
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
|
||||||
.HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'")
|
.HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'")
|
||||||
.HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'")
|
.HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'")
|
||||||
.HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'")
|
.HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'")
|
||||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogBrand", b =>
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@ -28,14 +29,14 @@ namespace Catalog.API.Infrastructure.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Brand")
|
b.Property<string>("Brand")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasAnnotation("MaxLength", 100);
|
.HasMaxLength(100);
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("CatalogBrand");
|
b.ToTable("CatalogBrand");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogItem", b =>
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@ -50,7 +51,7 @@ namespace Catalog.API.Infrastructure.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasAnnotation("MaxLength", 50);
|
.HasMaxLength(50);
|
||||||
|
|
||||||
b.Property<string>("PictureUri");
|
b.Property<string>("PictureUri");
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ namespace Catalog.API.Infrastructure.Migrations
|
|||||||
b.ToTable("Catalog");
|
b.ToTable("Catalog");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogType", b =>
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@ -74,21 +75,44 @@ namespace Catalog.API.Infrastructure.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Type")
|
b.Property<string>("Type")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasAnnotation("MaxLength", 100);
|
.HasMaxLength(100);
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("CatalogType");
|
b.ToTable("CatalogType");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogItem", b =>
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEvent", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogBrand", "CatalogBrand")
|
b.Property<Guid>("EventId")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationTime");
|
||||||
|
|
||||||
|
b.Property<string>("EventTypeName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
b.Property<int>("State");
|
||||||
|
|
||||||
|
b.Property<int>("TimesSent");
|
||||||
|
|
||||||
|
b.HasKey("EventId");
|
||||||
|
|
||||||
|
b.ToTable("IntegrationEvent");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("CatalogBrandId")
|
.HasForeignKey("CatalogBrandId")
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogType", "CatalogType")
|
b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("CatalogTypeId")
|
.HasForeignKey("CatalogTypeId")
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
@ -7,9 +7,11 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@ -73,6 +75,10 @@
|
|||||||
.AllowCredentials());
|
.AllowCredentials());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
|
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
|
||||||
|
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
|
||||||
|
|
||||||
services.AddMvc();
|
services.AddMvc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,5 +9,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API
|
|||||||
public class Settings
|
public class Settings
|
||||||
{
|
{
|
||||||
public string ExternalCatalogBaseUrl {get;set;}
|
public string ExternalCatalogBaseUrl {get;set;}
|
||||||
|
public string EventBusConnection { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog
|
||||||
|
{
|
||||||
|
public class ProductPriceChanged : IntegrationEventBase
|
||||||
|
{
|
||||||
|
public int ItemId { get; private set; }
|
||||||
|
|
||||||
|
public decimal NewPrice { get; private set; }
|
||||||
|
|
||||||
|
public decimal OldPrice { get; set; }
|
||||||
|
|
||||||
|
public ProductPriceChanged(int itemId, decimal newPrice, decimal oldPrice)
|
||||||
|
{
|
||||||
|
ItemId = itemId;
|
||||||
|
NewPrice = newPrice;
|
||||||
|
OldPrice = oldPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/Services/Common/Infrastructure/Data/EventStateEnum.cs
Normal file
13
src/Services/Common/Infrastructure/Data/EventStateEnum.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
|
||||||
|
{
|
||||||
|
public enum EventStateEnum
|
||||||
|
{
|
||||||
|
NotSend = 0,
|
||||||
|
Sent = 1,
|
||||||
|
SendingFailed = 2
|
||||||
|
}
|
||||||
|
}
|
26
src/Services/Common/Infrastructure/Data/IntegrationEvent.cs
Normal file
26
src/Services/Common/Infrastructure/Data/IntegrationEvent.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data
|
||||||
|
{
|
||||||
|
public class IntegrationEvent
|
||||||
|
{
|
||||||
|
public IntegrationEvent(IntegrationEventBase @event)
|
||||||
|
{
|
||||||
|
EventId = @event.Id;
|
||||||
|
CreationTime = DateTime.UtcNow;
|
||||||
|
EventTypeName = @event.GetType().FullName;
|
||||||
|
Content = JsonConvert.SerializeObject(@event);
|
||||||
|
State = EventStateEnum.NotSend;
|
||||||
|
TimesSent = 0;
|
||||||
|
}
|
||||||
|
public Guid EventId { get; private set; }
|
||||||
|
public string EventTypeName { get; private set; }
|
||||||
|
public EventStateEnum State { get; set; }
|
||||||
|
public int TimesSent { get; set; }
|
||||||
|
public DateTime CreationTime { get; private set; }
|
||||||
|
public string Content { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
154
src/Services/Common/Infrastructure/EventBusRabbitMQ.cs
Normal file
154
src/Services/Common/Infrastructure/EventBusRabbitMQ.cs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
|
||||||
|
using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
using RabbitMQ.Client.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
|
||||||
|
{
|
||||||
|
public class EventBusRabbitMQ : IEventBus
|
||||||
|
{
|
||||||
|
private readonly string _brokerName = "eshop_event_bus";
|
||||||
|
private readonly string _connectionString;
|
||||||
|
private readonly Dictionary<string, List<IIntegrationEventHandler>> _handlers;
|
||||||
|
private readonly List<Type> _eventTypes;
|
||||||
|
|
||||||
|
private Tuple<IModel, IConnection> _connection;
|
||||||
|
private string _queueName;
|
||||||
|
|
||||||
|
|
||||||
|
public EventBusRabbitMQ(string connectionString)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
_handlers = new Dictionary<string, List<IIntegrationEventHandler>>();
|
||||||
|
_eventTypes = new List<Type>();
|
||||||
|
}
|
||||||
|
public void Publish(IntegrationEventBase @event)
|
||||||
|
{
|
||||||
|
var eventName = @event.GetType().Name;
|
||||||
|
var factory = new ConnectionFactory() { HostName = _connectionString };
|
||||||
|
using (var connection = factory.CreateConnection())
|
||||||
|
using (var channel = connection.CreateModel())
|
||||||
|
{
|
||||||
|
channel.ExchangeDeclare(exchange: _brokerName,
|
||||||
|
type: "direct");
|
||||||
|
|
||||||
|
string message = JsonConvert.SerializeObject(@event);
|
||||||
|
var body = Encoding.UTF8.GetBytes(message);
|
||||||
|
|
||||||
|
channel.BasicPublish(exchange: _brokerName,
|
||||||
|
routingKey: eventName,
|
||||||
|
basicProperties: null,
|
||||||
|
body: body);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Subscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEventBase
|
||||||
|
{
|
||||||
|
var eventName = typeof(T).Name;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEventBase
|
||||||
|
{
|
||||||
|
var eventName = typeof(T).Name;
|
||||||
|
if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler))
|
||||||
|
{
|
||||||
|
_handlers[eventName].Remove(handler);
|
||||||
|
|
||||||
|
if (_handlers[eventName].Count == 0)
|
||||||
|
{
|
||||||
|
_handlers.Remove(eventName);
|
||||||
|
var eventType = _eventTypes.Single(e => e.Name == eventName);
|
||||||
|
_eventTypes.Remove(eventType);
|
||||||
|
_connection.Item1.QueueUnbind(queue: _queueName,
|
||||||
|
exchange: _brokerName,
|
||||||
|
routingKey: eventName);
|
||||||
|
|
||||||
|
if (_handlers.Keys.Count == 0)
|
||||||
|
{
|
||||||
|
_queueName = string.Empty;
|
||||||
|
_connection.Item1.Close();
|
||||||
|
_connection.Item2.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IModel GetChannel()
|
||||||
|
{
|
||||||
|
if (_connection != null)
|
||||||
|
{
|
||||||
|
return _connection.Item1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var factory = new ConnectionFactory() { HostName = _connectionString };
|
||||||
|
var connection = factory.CreateConnection();
|
||||||
|
var channel = connection.CreateModel();
|
||||||
|
|
||||||
|
channel.ExchangeDeclare(exchange: _brokerName,
|
||||||
|
type: "direct");
|
||||||
|
if (string.IsNullOrEmpty(_queueName))
|
||||||
|
{
|
||||||
|
_queueName = channel.QueueDeclare().QueueName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var consumer = new EventingBasicConsumer(channel);
|
||||||
|
consumer.Received += async (model, ea) =>
|
||||||
|
{
|
||||||
|
var eventName = ea.RoutingKey;
|
||||||
|
var message = Encoding.UTF8.GetString(ea.Body);
|
||||||
|
|
||||||
|
await ProcessEvent(eventName, message);
|
||||||
|
};
|
||||||
|
channel.BasicConsume(queue: _queueName,
|
||||||
|
noAck: true,
|
||||||
|
consumer: consumer);
|
||||||
|
_connection = new Tuple<IModel, IConnection>(channel, connection);
|
||||||
|
|
||||||
|
return _connection.Item1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessEvent(string eventName, string message)
|
||||||
|
{
|
||||||
|
if (_handlers.ContainsKey(eventName))
|
||||||
|
{
|
||||||
|
Type eventType = _eventTypes.Single(t => t.Name == eventName);
|
||||||
|
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
||||||
|
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||||
|
var handlers = _handlers[eventName];
|
||||||
|
|
||||||
|
foreach (var handler in handlers)
|
||||||
|
{
|
||||||
|
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/Services/Common/Infrastructure/IEventBus.cs
Normal file
13
src/Services/Common/Infrastructure/IEventBus.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IEventBus
|
||||||
|
{
|
||||||
|
void Subscribe<T>(IIntegrationEventHandler<T> handler) where T: IntegrationEventBase;
|
||||||
|
void Unsubscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEventBase;
|
||||||
|
void Publish(IntegrationEventBase @event);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IIntegrationEventHandler<in TIntegrationEvent> : IIntegrationEventHandler
|
||||||
|
where TIntegrationEvent: IntegrationEventBase
|
||||||
|
{
|
||||||
|
Task Handle(TIntegrationEvent @event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IIntegrationEventHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
11
src/Services/Common/Infrastructure/Infrastructure.csproj
Normal file
11
src/Services/Common/Infrastructure/Infrastructure.csproj
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||||
|
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
|
||||||
|
<RootNamespace>Microsoft.eShopOnContainers.Services.Common.Infrastructure</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||||
|
<PackageReference Include="RabbitMQ.Client" Version="4.1.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
16
src/Services/Common/Infrastructure/IntegrationEventBase.cs
Normal file
16
src/Services/Common/Infrastructure/IntegrationEventBase.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure
|
||||||
|
{
|
||||||
|
public class IntegrationEventBase
|
||||||
|
{
|
||||||
|
public IntegrationEventBase()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid Id { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ using System.Threading.Tasks;
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
|
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
|
||||||
{
|
{
|
||||||
[Route("api/v1/[controller]")]
|
[Route("api/v1/[controller]")]
|
||||||
//[Authorize]
|
[Authorize]
|
||||||
public class OrdersController : Controller
|
public class OrdersController : Controller
|
||||||
{
|
{
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
@ -11,6 +11,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
|
|||||||
public string ProductId { get; set; }
|
public string ProductId { get; set; }
|
||||||
public string ProductName { get; set; }
|
public string ProductName { get; set; }
|
||||||
public decimal UnitPrice { get; set; }
|
public decimal UnitPrice { get; set; }
|
||||||
|
public decimal OldUnitPrice { get; set; }
|
||||||
public int Quantity { get; set; }
|
public int Quantity { get; set; }
|
||||||
public string PictureUrl { get; set; }
|
public string PictureUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -18,17 +18,24 @@
|
|||||||
var item = Model.Items[i];
|
var item = Model.Items[i];
|
||||||
|
|
||||||
<article class="esh-basket-items esh-basket-items--border row">
|
<article class="esh-basket-items esh-basket-items--border row">
|
||||||
|
<div>
|
||||||
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
|
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
|
||||||
<img class="esh-basket-image" src="@item.PictureUrl" />
|
<img class="esh-basket-image" src="@item.PictureUrl" />
|
||||||
</section>
|
</section>
|
||||||
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section>
|
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section>
|
||||||
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice</section>
|
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice</section>
|
||||||
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
|
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
|
||||||
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
|
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
|
||||||
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
|
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
|
||||||
</section>
|
</section>
|
||||||
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2)</section>
|
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2)</section>
|
||||||
|
</div>
|
||||||
|
@if (item.OldUnitPrice != 0)
|
||||||
|
{
|
||||||
|
<div class="esh-basket-item">
|
||||||
|
The price of the item has changed. Old price was @item.OldUnitPrice $
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</article>
|
</article>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\src\Services\Catalog\Catalog.API\Catalog.API.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" />
|
<ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Web\WebMVC\WebMVC.csproj" />
|
<ProjectReference Include="..\..\..\src\Web\WebMVC\WebMVC.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -17,7 +17,7 @@ namespace FunctionalTests.Middleware
|
|||||||
|
|
||||||
public async Task Invoke(HttpContext httpContext)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var identity = new ClaimsIdentity();
|
var identity = new ClaimsIdentity("cookies");
|
||||||
identity.AddClaim(new Claim("sub", "1234"));
|
identity.AddClaim(new Claim("sub", "1234"));
|
||||||
httpContext.User.AddIdentity(identity);
|
httpContext.User.AddIdentity(identity);
|
||||||
await _next.Invoke(httpContext);
|
await _next.Invoke(httpContext);
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace FunctionalTests.Services.Catalog
|
||||||
|
{
|
||||||
|
public class CatalogScenarios : CatalogScenariosBase
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Post_update_a_catalogitem_price_and_catalogitem_is_returned_modified()
|
||||||
|
{
|
||||||
|
using (var server = CreateServer())
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var itemToModify = GetCatalogItem();
|
||||||
|
var newPrice = new Random().Next(1, 200);
|
||||||
|
itemToModify.Price = newPrice;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var postRes = await client.PostAsync(Post.UpdateCatalogProduct,
|
||||||
|
new StringContent(JsonConvert.SerializeObject(itemToModify),
|
||||||
|
UTF8Encoding.UTF8, "application/json"));
|
||||||
|
var response = await client.GetAsync(Get.ProductByName(itemToModify.Name));
|
||||||
|
var result = JsonConvert.DeserializeObject<PaginatedItemsViewModel<CatalogItem>>(await response.Content.ReadAsStringAsync());
|
||||||
|
var item = result.Data.First();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(result.Count, 1);
|
||||||
|
Assert.Equal(itemToModify.Id, item.Id);
|
||||||
|
Assert.Equal(newPrice, item.Price);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private CatalogItem GetCatalogItem()
|
||||||
|
{
|
||||||
|
return new CatalogItem()
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Price = 12.5M,
|
||||||
|
Name = ".NET Bot Black Sweatshirt"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
using FunctionalTests.Middleware;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Catalog.API;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace FunctionalTests.Services.Catalog
|
||||||
|
{
|
||||||
|
public class CatalogScenariosBase
|
||||||
|
{
|
||||||
|
public TestServer CreateServer()
|
||||||
|
{
|
||||||
|
var webHostBuilder = new WebHostBuilder();
|
||||||
|
webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
||||||
|
webHostBuilder.UseStartup<Startup>();
|
||||||
|
|
||||||
|
return new TestServer(webHostBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Get
|
||||||
|
{
|
||||||
|
public static string Orders = "api/v1/orders";
|
||||||
|
|
||||||
|
public static string ProductByName(string name)
|
||||||
|
{
|
||||||
|
return $"api/v1/catalog/items/withname/{name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Post
|
||||||
|
{
|
||||||
|
public static string UpdateCatalogProduct = "api/v1/catalog";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.eShopOnContainers.Services.Catalog.API;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
||||||
|
namespace FunctionalTests.Services.Catalog
|
||||||
|
{
|
||||||
|
public class CatalogTestsStartup : Startup
|
||||||
|
{
|
||||||
|
public CatalogTestsStartup(IHostingEnvironment env) : base(env)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
|
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
|
||||||
"IdentityUrl": "http://localhost:5105",
|
"IdentityUrl": "http://localhost:5105",
|
||||||
"isTest": "true"
|
"isTest": "true",
|
||||||
|
"EventBusConnection": "localhost"
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ namespace IntegrationTests.Middleware
|
|||||||
|
|
||||||
public async Task Invoke(HttpContext httpContext)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var identity = new ClaimsIdentity();
|
var identity = new ClaimsIdentity("cookies");
|
||||||
identity.AddClaim(new Claim("sub", "1234"));
|
identity.AddClaim(new Claim("sub", "1234"));
|
||||||
httpContext.User.AddIdentity(identity);
|
httpContext.User.AddIdentity(identity);
|
||||||
await _next.Invoke(httpContext);
|
await _next.Invoke(httpContext);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user