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
 | 
			
		||||
      - 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. 
 | 
			
		||||
      - EventBusConnection=rabbitmq
 | 
			
		||||
    ports:
 | 
			
		||||
      - "5103:5103"
 | 
			
		||||
 | 
			
		||||
@ -23,6 +24,7 @@ services:
 | 
			
		||||
      - ASPNETCORE_URLS=http://0.0.0.0:5101
 | 
			
		||||
      - 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.	  
 | 
			
		||||
      - EventBusConnection=rabbitmq
 | 
			
		||||
    ports:
 | 
			
		||||
      - "5101:5101"
 | 
			
		||||
 | 
			
		||||
@ -42,6 +44,7 @@ services:
 | 
			
		||||
      - ASPNETCORE_URLS=http://0.0.0.0:5102
 | 
			
		||||
      - 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. 
 | 
			
		||||
      - EventBusConnection=rabbitmq
 | 
			
		||||
    ports:
 | 
			
		||||
      - "5102:5102"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ services:
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - basket.data
 | 
			
		||||
      - identity.api
 | 
			
		||||
      - rabbitmq
 | 
			
		||||
 | 
			
		||||
  catalog.api:
 | 
			
		||||
    image: eshop/catalog.api
 | 
			
		||||
@ -17,6 +18,7 @@ services:
 | 
			
		||||
      dockerfile: Dockerfile
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - sql.data
 | 
			
		||||
      - rabbitmq
 | 
			
		||||
 | 
			
		||||
  identity.api:
 | 
			
		||||
    image: eshop/identity.api
 | 
			
		||||
@ -61,3 +63,8 @@ services:
 | 
			
		||||
    image: redis
 | 
			
		||||
    ports:
 | 
			
		||||
      - "6379:6379"
 | 
			
		||||
 | 
			
		||||
  rabbitmq:
 | 
			
		||||
    image: rabbitmq
 | 
			
		||||
    ports:
 | 
			
		||||
      - "5672:5672"
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTests", "test\Se
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Services\FunctionalTests\FunctionalTests.csproj", "{CFE2FACB-4538-4B99-8A10-306F3882952D}"
 | 
			
		||||
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
 | 
			
		||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
			
		||||
		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|x86.ActiveCfg = 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
 | 
			
		||||
	GlobalSection(SolutionProperties) = preSolution
 | 
			
		||||
		HideSolutionNode = FALSE
 | 
			
		||||
@ -567,5 +623,8 @@ Global
 | 
			
		||||
		{F16E3C6A-1C94-4EAB-BE91-099618060B68} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04}
 | 
			
		||||
		{5B810E3D-112E-4857-B197-F09D2FD41E27} = {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
 | 
			
		||||
EndGlobal
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,10 @@
 | 
			
		||||
    <PackageReference Include="Swashbuckle" Version="6.0.0-beta902" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\..\Common\Infrastructure\Infrastructure.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <None Update="Dockerfile">
 | 
			
		||||
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
 | 
			
		||||
 | 
			
		||||
@ -8,5 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
 | 
			
		||||
    public class BasketSettings
 | 
			
		||||
    {
 | 
			
		||||
        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 ProductName { get; set; }
 | 
			
		||||
        public decimal UnitPrice { get; set; }
 | 
			
		||||
        public decimal OldUnitPrice { get; set; }
 | 
			
		||||
        public int Quantity { get; set; }
 | 
			
		||||
        public string PictureUrl { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
 | 
			
		||||
    public interface IBasketRepository
 | 
			
		||||
    {
 | 
			
		||||
        Task<CustomerBasket> GetBasket(string customerId);
 | 
			
		||||
        Task<IEnumerable<string>> GetUsers();
 | 
			
		||||
        Task<CustomerBasket> UpdateBasket(CustomerBasket basket);
 | 
			
		||||
        Task<bool> DeleteBasket(string id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,18 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
 | 
			
		||||
            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)
 | 
			
		||||
        {
 | 
			
		||||
            var database = await GetDatabase();
 | 
			
		||||
@ -63,14 +75,32 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
 | 
			
		||||
        {
 | 
			
		||||
            if (_redis == null)
 | 
			
		||||
            {
 | 
			
		||||
                //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());
 | 
			
		||||
                await ConnectToRedisAsync();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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 Swashbuckle.Swagger.Model;
 | 
			
		||||
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
 | 
			
		||||
{
 | 
			
		||||
@ -76,6 +79,17 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            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.
 | 
			
		||||
 | 
			
		||||
@ -45,6 +45,7 @@
 | 
			
		||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.0" />
 | 
			
		||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" 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" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
@ -52,6 +53,10 @@
 | 
			
		||||
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ProjectReference Include="..\..\Common\Infrastructure\Infrastructure.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <None Update="Dockerfile">
 | 
			
		||||
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,11 @@ using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
 | 
			
		||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
 | 
			
		||||
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 System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
@ -15,11 +19,13 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
 | 
			
		||||
    {
 | 
			
		||||
        private readonly CatalogContext _context;
 | 
			
		||||
        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;
 | 
			
		||||
            _settings = settings;
 | 
			
		||||
            _eventBus = eventBus;
 | 
			
		||||
 | 
			
		||||
            ((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
 | 
			
		||||
        }
 | 
			
		||||
@ -125,6 +131,31 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
 | 
			
		||||
            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) {
 | 
			
		||||
            var baseUri = _settings.Value.ExternalCatalogBaseUrl;
 | 
			
		||||
            items.ForEach(x =>
 | 
			
		||||
@ -134,5 +165,22 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
 | 
			
		||||
 | 
			
		||||
            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 Microsoft.EntityFrameworkCore;
 | 
			
		||||
    using Model;
 | 
			
		||||
    using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data;
 | 
			
		||||
 | 
			
		||||
    public class CatalogContext : DbContext
 | 
			
		||||
    {
 | 
			
		||||
@ -12,11 +13,14 @@
 | 
			
		||||
        public DbSet<CatalogItem> CatalogItems { get; set; }
 | 
			
		||||
        public DbSet<CatalogBrand> CatalogBrands { get; set; }
 | 
			
		||||
        public DbSet<CatalogType> CatalogTypes { get; set; }
 | 
			
		||||
        public DbSet<IntegrationEvent> IntegrationEvents { get; set; }
 | 
			
		||||
 | 
			
		||||
        protected override void OnModelCreating(ModelBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
 | 
			
		||||
            builder.Entity<CatalogType>(ConfigureCatalogType);
 | 
			
		||||
            builder.Entity<CatalogItem>(ConfigureCatalogItem);
 | 
			
		||||
            builder.Entity<IntegrationEvent>(ConfigureIntegrationEvent);
 | 
			
		||||
        }     
 | 
			
		||||
 | 
			
		||||
        void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
 | 
			
		||||
@ -75,5 +79,31 @@
 | 
			
		||||
                .IsRequired()
 | 
			
		||||
                .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.Migrations;
 | 
			
		||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
 | 
			
		||||
using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
 | 
			
		||||
 | 
			
		||||
namespace Catalog.API.Infrastructure.Migrations
 | 
			
		||||
{
 | 
			
		||||
@ -13,13 +14,13 @@ namespace Catalog.API.Infrastructure.Migrations
 | 
			
		||||
        protected override void BuildModel(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_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.Infrastructure.CatalogBrand", b =>
 | 
			
		||||
            modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
@ -28,14 +29,14 @@ namespace Catalog.API.Infrastructure.Migrations
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Brand")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasAnnotation("MaxLength", 100);
 | 
			
		||||
                        .HasMaxLength(100);
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    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")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
@ -50,7 +51,7 @@ namespace Catalog.API.Infrastructure.Migrations
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasAnnotation("MaxLength", 50);
 | 
			
		||||
                        .HasMaxLength(50);
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("PictureUri");
 | 
			
		||||
 | 
			
		||||
@ -65,7 +66,7 @@ namespace Catalog.API.Infrastructure.Migrations
 | 
			
		||||
                    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")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
@ -74,21 +75,44 @@ namespace Catalog.API.Infrastructure.Migrations
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Type")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasAnnotation("MaxLength", 100);
 | 
			
		||||
                        .HasMaxLength(100);
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    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()
 | 
			
		||||
                        .HasForeignKey("CatalogBrandId")
 | 
			
		||||
                        .OnDelete(DeleteBehavior.Cascade);
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogType", "CatalogType")
 | 
			
		||||
                    b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("CatalogTypeId")
 | 
			
		||||
                        .OnDelete(DeleteBehavior.Cascade);
 | 
			
		||||
 | 
			
		||||
@ -7,9 +7,11 @@
 | 
			
		||||
    using Microsoft.EntityFrameworkCore;
 | 
			
		||||
    using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
    using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
 | 
			
		||||
    using Microsoft.eShopOnContainers.Services.Common.Infrastructure;
 | 
			
		||||
    using Microsoft.Extensions.Configuration;
 | 
			
		||||
    using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
    using Microsoft.Extensions.Logging;
 | 
			
		||||
    using Microsoft.Extensions.Options;
 | 
			
		||||
    using System;
 | 
			
		||||
    using System.IO;
 | 
			
		||||
    using System.Reflection;
 | 
			
		||||
@ -73,6 +75,10 @@
 | 
			
		||||
                    .AllowCredentials());
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var serviceProvider = services.BuildServiceProvider();
 | 
			
		||||
            var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
 | 
			
		||||
            services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
 | 
			
		||||
            
 | 
			
		||||
            services.AddMvc();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,5 +9,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API
 | 
			
		||||
    public class Settings
 | 
			
		||||
    {
 | 
			
		||||
        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
 | 
			
		||||
{
 | 
			
		||||
    [Route("api/v1/[controller]")]
 | 
			
		||||
    //[Authorize]
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    public class OrdersController : Controller
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IMediator _mediator;
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
 | 
			
		||||
        public string ProductId { get; set; }
 | 
			
		||||
        public string ProductName { get; set; }
 | 
			
		||||
        public decimal UnitPrice { get; set; }
 | 
			
		||||
        public decimal OldUnitPrice { get; set; }
 | 
			
		||||
        public int Quantity { get; set; }
 | 
			
		||||
        public string PictureUrl { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -18,17 +18,24 @@
 | 
			
		||||
        var item = Model.Items[i];
 | 
			
		||||
 | 
			
		||||
        <article class="esh-basket-items esh-basket-items--border row">
 | 
			
		||||
 | 
			
		||||
            <section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
 | 
			
		||||
                <img class="esh-basket-image" src="@item.PictureUrl" />
 | 
			
		||||
            </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">
 | 
			
		||||
                <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" />
 | 
			
		||||
            </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>
 | 
			
		||||
                <section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
 | 
			
		||||
                    <img class="esh-basket-image" src="@item.PictureUrl" />
 | 
			
		||||
                </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">
 | 
			
		||||
                    <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" />
 | 
			
		||||
                </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>
 | 
			
		||||
    }
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@
 | 
			
		||||
  </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\Web\WebMVC\WebMVC.csproj" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ namespace FunctionalTests.Middleware
 | 
			
		||||
 | 
			
		||||
        public async Task Invoke(HttpContext httpContext)
 | 
			
		||||
        {
 | 
			
		||||
            var identity = new ClaimsIdentity();
 | 
			
		||||
            var identity = new ClaimsIdentity("cookies");
 | 
			
		||||
            identity.AddClaim(new Claim("sub", "1234"));
 | 
			
		||||
            httpContext.User.AddIdentity(identity);
 | 
			
		||||
            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;",
 | 
			
		||||
  "IdentityUrl": "http://localhost:5105",
 | 
			
		||||
  "isTest": "true"
 | 
			
		||||
  "isTest": "true",
 | 
			
		||||
  "EventBusConnection": "localhost"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ namespace IntegrationTests.Middleware
 | 
			
		||||
 | 
			
		||||
        public async Task Invoke(HttpContext httpContext)
 | 
			
		||||
        {
 | 
			
		||||
            var identity = new ClaimsIdentity();
 | 
			
		||||
            var identity = new ClaimsIdentity("cookies");
 | 
			
		||||
            identity.AddClaim(new Claim("sub", "1234"));
 | 
			
		||||
            httpContext.User.AddIdentity(identity);
 | 
			
		||||
            await _next.Invoke(httpContext);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user