diff --git a/docker-compose-external.override.yml b/docker-compose-external.override.yml
new file mode 100644
index 000000000..f20440ad7
--- /dev/null
+++ b/docker-compose-external.override.yml
@@ -0,0 +1,9 @@
+version: '2'
+
+services:
+ sql.data:
+ environment:
+ - SA_PASSWORD=Pass@word
+ - ACCEPT_EULA=Y
+ ports:
+ - "5433:1433"
\ No newline at end of file
diff --git a/docker-compose-external.yml b/docker-compose-external.yml
new file mode 100644
index 000000000..6bce32a3e
--- /dev/null
+++ b/docker-compose-external.yml
@@ -0,0 +1,10 @@
+version: '2'
+
+services:
+ sql.data:
+ image: microsoft/mssql-server-linux
+
+ basket.data:
+ image: redis
+ ports:
+ - "6379:6379"
diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln
index 0f959d467..c92dc8c7d 100644
--- a/eShopOnContainers-ServicesAndWebApps.sln
+++ b/eShopOnContainers-ServicesAndWebApps.sln
@@ -48,8 +48,6 @@ Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-co
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSPA", "src\Web\WebSPA\WebSPA.csproj", "{F16E3C6A-1C94-4EAB-BE91-099618060B68}"
EndProject
-Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose-windows", "docker-compose-windows.dcproj", "{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@@ -496,54 +494,6 @@ Global
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Release|x64.Build.0 = Release|Any CPU
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Release|x86.ActiveCfg = Release|Any CPU
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Release|x86.Build.0 = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|Any CPU.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|ARM.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|ARM.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|iPhone.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|x64.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|x64.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|x86.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|x86.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|ARM.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|iPhone.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|x64.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|x64.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|x86.ActiveCfg = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|x86.Build.0 = Debug|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|Any CPU.Build.0 = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|ARM.ActiveCfg = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|ARM.Build.0 = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|iPhone.ActiveCfg = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|iPhone.Build.0 = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|x64.ActiveCfg = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|x64.Build.0 = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|x86.ActiveCfg = Release|Any CPU
- {2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj
index 15be6a8c2..6811e43cb 100644
--- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj
+++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj
@@ -49,7 +49,7 @@
-
+
diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs
index 38bea3ee3..2b44d0183 100644
--- a/src/Services/Catalog/Catalog.API/Startup.cs
+++ b/src/Services/Catalog/Catalog.API/Startup.cs
@@ -12,6 +12,7 @@
using Microsoft.Extensions.Logging;
using System;
using System.IO;
+ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -28,7 +29,7 @@
if (env.IsDevelopment())
{
- builder.AddUserSecrets();
+ builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
}
builder.AddEnvironmentVariables();
diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs
index 3bcb2425b..5633c3b37 100644
--- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs
+++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs
@@ -64,7 +64,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
_orderItems = new List();
}
- public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
+ public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
string cardNumber, string cardHolderName, DateTime cardExpiration,
string cardSecurityNumber, int cardTypeId) : this()
{
diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
index a73378735..bf8e90ea6 100644
--- a/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
+++ b/src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
@@ -4,9 +4,23 @@
using Domain.AggregatesModel.OrderAggregate;
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
+ using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories;
using System;
using System.Threading.Tasks;
+
+ public class CreateOrderCommandIdentifiedHandler : IdentifierCommandHandler
+ {
+ public CreateOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
+ {
+ }
+
+ protected override bool CreateResultForDuplicateRequest()
+ {
+ return true; // Ignore duplicate requests for creating order.
+ }
+ }
+
public class CreateOrderCommandHandler
: IAsyncRequestHandler
{
diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommand.cs b/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommand.cs
new file mode 100644
index 000000000..823e10b69
--- /dev/null
+++ b/src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommand.cs
@@ -0,0 +1,20 @@
+using MediatR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
+{
+ public class IdentifiedCommand : IAsyncRequest
+ where T : IAsyncRequest
+ {
+ public T Command { get; }
+ public Guid Id { get; }
+ public IdentifiedCommand(T command, Guid id)
+ {
+ Command = command;
+ Id = id;
+ }
+ }
+}
diff --git a/src/Services/Ordering/Ordering.API/Application/Commands/IdentifierCommandHandler.cs b/src/Services/Ordering/Ordering.API/Application/Commands/IdentifierCommandHandler.cs
new file mode 100644
index 000000000..5fdc0181b
--- /dev/null
+++ b/src/Services/Ordering/Ordering.API/Application/Commands/IdentifierCommandHandler.cs
@@ -0,0 +1,60 @@
+using MediatR;
+using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
+{
+ ///
+ /// Provides a base implementation for handling duplicate request and ensuring idempotent updates, in the cases where
+ /// a requestid sent by client is used to detect duplicate requests.
+ ///
+ /// Type of the command handler that performs the operation if request is not duplicated
+ /// Return value of the inner command handler
+ public class IdentifierCommandHandler : IAsyncRequestHandler, R>
+ where T : IAsyncRequest
+ {
+ private readonly IMediator _mediator;
+ private readonly IRequestManager _requestManager;
+
+ public IdentifierCommandHandler(IMediator mediator, IRequestManager requestManager)
+ {
+ _mediator = mediator;
+ _requestManager = requestManager;
+ }
+
+ ///
+ /// Creates the result value to return if a previous request was found
+ ///
+ ///
+ protected virtual R CreateResultForDuplicateRequest()
+ {
+ return default(R);
+ }
+
+ ///
+ /// This method handles the command. It just ensures that no other request exists with the same ID, and if this is the case
+ /// just enqueues the original inner command.
+ ///
+ /// IdentifiedCommand which contains both original command & request ID
+ /// Return value of inner command or default value if request same ID was found
+ public async Task Handle(IdentifiedCommand message)
+ {
+ var alreadyExists = await _requestManager.ExistAsync(message.Id);
+ if (alreadyExists)
+ {
+ return CreateResultForDuplicateRequest();
+ }
+ else
+ {
+ await _requestManager.CreateRequestForCommandAsync(message.Id);
+ var result = await _mediator.SendAsync(message.Command);
+ return result;
+ }
+ }
+ }
+
+
+}
diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
index 4944617bd..df2dc23f7 100644
--- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
+++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
@@ -28,9 +28,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[Route("new")]
[HttpPost]
- public async Task CreateOrder([FromBody]CreateOrderCommand createOrderCommand)
+ public async Task CreateOrder([FromBody]CreateOrderCommand createOrderCommand, [FromHeader(Name = "x-requestid")] string requestId)
{
- var result = await _mediator.SendAsync(createOrderCommand);
+ bool result = false;
+ if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
+ {
+ var requestCreateOrder = new IdentifiedCommand(createOrderCommand, guid);
+ result = await _mediator.SendAsync(requestCreateOrder);
+ }
if (result)
{
return Ok();
diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/ApplicationModule.cs b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/ApplicationModule.cs
index a953c2d2d..0d8e34476 100644
--- a/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/ApplicationModule.cs
+++ b/src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/ApplicationModule.cs
@@ -33,6 +33,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
builder.RegisterType()
.As>()
.InstancePerLifetimeScope();
+
+ builder.RegisterType()
+ .As()
+ .InstancePerLifetimeScope();
}
}
}
diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.Designer.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.Designer.cs
new file mode 100644
index 000000000..84d560ddc
--- /dev/null
+++ b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.Designer.cs
@@ -0,0 +1,245 @@
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
+
+namespace Ordering.API.Migrations
+{
+ [DbContext(typeof(OrderingContext))]
+ [Migration("20170303085729_RequestsTable")]
+ partial class RequestsTable
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+ modelBuilder
+ .HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
+ .HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'")
+ .HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
+ .HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
+ .HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq")
+ .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
+
+ b.Property("IdentityGuid")
+ .IsRequired()
+ .HasMaxLength(200);
+
+ b.HasKey("Id");
+
+ b.HasIndex("IdentityGuid")
+ .IsUnique();
+
+ b.ToTable("buyers","ordering");
+ });
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", b =>
+ {
+ b.Property("Id")
+ .HasDefaultValue(1);
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200);
+
+ b.HasKey("Id");
+
+ b.ToTable("cardtypes","ordering");
+ });
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq")
+ .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
+
+ b.Property("Alias")
+ .IsRequired()
+ .HasMaxLength(200);
+
+ b.Property("BuyerId");
+
+ b.Property("CardHolderName")
+ .IsRequired()
+ .HasMaxLength(200);
+
+ b.Property("CardNumber")
+ .IsRequired()
+ .HasMaxLength(25);
+
+ b.Property("CardTypeId");
+
+ b.Property("Expiration");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BuyerId");
+
+ b.HasIndex("CardTypeId");
+
+ b.ToTable("paymentmethods","ordering");
+ });
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("City");
+
+ b.Property("Country");
+
+ b.Property("State");
+
+ b.Property("Street");
+
+ b.Property("ZipCode");
+
+ b.HasKey("Id");
+
+ b.ToTable("address","ordering");
+ });
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasAnnotation("SqlServer:HiLoSequenceName", "orderseq")
+ .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
+
+ b.Property("AddressId");
+
+ b.Property("BuyerId");
+
+ b.Property("OrderDate");
+
+ b.Property("OrderStatusId");
+
+ b.Property("PaymentMethodId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AddressId");
+
+ b.HasIndex("BuyerId");
+
+ b.HasIndex("OrderStatusId");
+
+ b.HasIndex("PaymentMethodId");
+
+ b.ToTable("orders","ordering");
+ });
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq")
+ .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
+
+ b.Property("Discount");
+
+ b.Property("OrderId");
+
+ b.Property("PictureUrl");
+
+ b.Property("ProductId");
+
+ b.Property("ProductName")
+ .IsRequired();
+
+ b.Property("UnitPrice");
+
+ b.Property("Units");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OrderId");
+
+ b.ToTable("orderItems","ordering");
+ });
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b =>
+ {
+ b.Property("Id")
+ .HasDefaultValue(1);
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200);
+
+ b.HasKey("Id");
+
+ b.ToTable("orderstatus","ordering");
+ });
+
+ modelBuilder.Entity("Ordering.Infrastructure.ClientRequest", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Name")
+ .IsRequired();
+
+ b.Property("Time");
+
+ b.HasKey("Id");
+
+ b.ToTable("requests","ordering");
+ });
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
+ {
+ b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
+ .WithMany("PaymentMethods")
+ .HasForeignKey("BuyerId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", "CardType")
+ .WithMany()
+ .HasForeignKey("CardTypeId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
+ {
+ b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address")
+ .WithMany()
+ .HasForeignKey("AddressId");
+
+ b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer")
+ .WithMany()
+ .HasForeignKey("BuyerId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus")
+ .WithMany()
+ .HasForeignKey("OrderStatusId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", "PaymentMethod")
+ .WithMany()
+ .HasForeignKey("PaymentMethodId");
+ });
+
+ modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
+ {
+ b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order")
+ .WithMany("OrderItems")
+ .HasForeignKey("OrderId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+ }
+ }
+}
diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.cs
new file mode 100644
index 000000000..5e5918c88
--- /dev/null
+++ b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Ordering.API.Migrations
+{
+ public partial class RequestsTable : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "requests",
+ schema: "ordering",
+ columns: table => new
+ {
+ Id = table.Column(nullable: false),
+ Name = table.Column(nullable: false),
+ Time = table.Column(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_requests", x => x.Id);
+ });
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "requests",
+ schema: "ordering");
+ }
+ }
+}
diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs
index 5c7b6d25b..654f93d6d 100644
--- a/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs
+++ b/src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs
@@ -183,6 +183,21 @@ namespace Ordering.API.Migrations
b.ToTable("orderstatus","ordering");
});
+ modelBuilder.Entity("Ordering.Infrastructure.ClientRequest", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Name")
+ .IsRequired();
+
+ b.Property("Time");
+
+ b.HasKey("Id");
+
+ b.ToTable("requests","ordering");
+ });
+
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.csproj b/src/Services/Ordering/Ordering.API/Ordering.API.csproj
index d83aed609..ce710c6a0 100644
--- a/src/Services/Ordering/Ordering.API/Ordering.API.csproj
+++ b/src/Services/Ordering/Ordering.API/Ordering.API.csproj
@@ -51,10 +51,11 @@
+
-
+
diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs
index 539647514..0f75b6370 100644
--- a/src/Services/Ordering/Ordering.API/Startup.cs
+++ b/src/Services/Ordering/Ordering.API/Startup.cs
@@ -29,7 +29,7 @@
if (env.IsDevelopment())
{
- builder.AddUserSecrets();
+ builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
}
builder.AddEnvironmentVariables();
@@ -67,7 +67,7 @@
Title = "Ordering HTTP API",
Version = "v1",
Description = "The Ordering Service HTTP API",
- TermsOfService = "Terms Of Service"
+ TermsOfService = "Terms Of Service"
});
});
@@ -82,7 +82,7 @@
// Add application services.
services.AddSingleton();
- services.AddTransient();
+ services.AddTransient();
services.AddOptions();
@@ -92,7 +92,7 @@
container.Populate(services);
container.RegisterModule(new MediatorModule());
- container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"] ));
+ container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"]));
return new AutofacServiceProvider(container.Build());
}
diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
index 658f18967..fecc53d44 100644
--- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
+++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
@@ -22,6 +22,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public OrderStatus OrderStatus { get; private set; }
private int _orderStatusId;
+
// DDD Patterns comment
// Using a private collection field, better for DDD Aggregate's encapsulation
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
@@ -46,7 +47,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
_paymentMethodId = paymentMethodId;
_orderStatusId = OrderStatus.InProcess.Id;
_orderDate = DateTime.UtcNow;
-
Address = address;
}
diff --git a/src/Services/Ordering/Ordering.Infrastructure/ClientRequest.cs b/src/Services/Ordering/Ordering.Infrastructure/ClientRequest.cs
new file mode 100644
index 000000000..47a401aab
--- /dev/null
+++ b/src/Services/Ordering/Ordering.Infrastructure/ClientRequest.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
+{
+ public class ClientRequest
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ public DateTime Time { get; set; }
+ }
+}
diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
index 73d831eaf..7a9d192e4 100644
--- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
+++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
@@ -30,6 +30,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
+
+ modelBuilder.Entity(ConfigureRequests);
modelBuilder.Entity(ConfigureAddress);
modelBuilder.Entity(ConfigurePayment);
modelBuilder.Entity(ConfigureOrder);
@@ -39,6 +41,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
modelBuilder.Entity(ConfigureBuyer);
}
+ private void ConfigureRequests(EntityTypeBuilder requestConfiguration)
+ {
+ requestConfiguration.ToTable("requests", DEFAULT_SCHEMA);
+ requestConfiguration.HasKey(cr => cr.Id);
+ requestConfiguration.Property(cr => cr.Name).IsRequired();
+ requestConfiguration.Property(cr => cr.Time).IsRequired();
+ }
+
void ConfigureAddress(EntityTypeBuilder addressConfiguration)
{
addressConfiguration.ToTable("address", DEFAULT_SCHEMA);
diff --git a/src/Services/Ordering/Ordering.Infrastructure/Repositories/IRequestManager.cs b/src/Services/Ordering/Ordering.Infrastructure/Repositories/IRequestManager.cs
new file mode 100644
index 000000000..ecb144695
--- /dev/null
+++ b/src/Services/Ordering/Ordering.Infrastructure/Repositories/IRequestManager.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
+{
+ public interface IRequestManager
+ {
+ Task ExistAsync(Guid id);
+ Task CreateRequestForCommandAsync(Guid id);
+ }
+}
diff --git a/src/Services/Ordering/Ordering.Infrastructure/Repositories/RequestManager.cs b/src/Services/Ordering/Ordering.Infrastructure/Repositories/RequestManager.cs
new file mode 100644
index 000000000..1661ab6e5
--- /dev/null
+++ b/src/Services/Ordering/Ordering.Infrastructure/Repositories/RequestManager.cs
@@ -0,0 +1,42 @@
+using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
+{
+ public class RequestManager : IRequestManager
+ {
+ private readonly OrderingContext _context;
+ public RequestManager(OrderingContext ctx)
+ {
+ _context = ctx;
+ }
+
+
+ public async Task ExistAsync(Guid id)
+ {
+ var request = await _context.FindAsync(id);
+ return request != null;
+ }
+
+ public async Task CreateRequestForCommandAsync(Guid id)
+ {
+
+ var exists = await ExistAsync(id);
+ var request = exists ?
+ throw new Exception($"Request with {id} already exists") :
+ new ClientRequest()
+ {
+ Id = id,
+ Name = typeof(T).Name,
+ Time = DateTime.UtcNow
+ };
+
+ _context.Add(request);
+ await _context.SaveChangesAsync();
+ }
+
+ }
+}
diff --git a/src/Web/WebMVC/Models/Order.cs b/src/Web/WebMVC/Models/Order.cs
index cfcae8a36..bddc740e3 100644
--- a/src/Web/WebMVC/Models/Order.cs
+++ b/src/Web/WebMVC/Models/Order.cs
@@ -55,6 +55,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Models
public List OrderItems { get; }
+ [Required]
+ public Guid RequestId { get; set; }
+
+
public void CardExpirationShortFormat()
{
CardExpirationShort = CardExpiration.ToString("MM/yy");
diff --git a/src/Web/WebMVC/Services/OrderingService.cs b/src/Web/WebMVC/Services/OrderingService.cs
index 652ee18b0..4c816203c 100644
--- a/src/Web/WebMVC/Services/OrderingService.cs
+++ b/src/Web/WebMVC/Services/OrderingService.cs
@@ -79,14 +79,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
_apiClient = new HttpClient();
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
-
+ _apiClient.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString());
var ordersUrl = $"{_remoteServiceBaseUrl}/new";
order.CardTypeId = 1;
order.CardExpirationApiFormat();
SetFakeIdToProducts(order);
StringContent content = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
-
+
var response = await _apiClient.PostAsync(ordersUrl, content);
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
diff --git a/src/Web/WebMVC/Views/Order/Create.cshtml b/src/Web/WebMVC/Views/Order/Create.cshtml
index f35262ccd..1aab06505 100644
--- a/src/Web/WebMVC/Views/Order/Create.cshtml
+++ b/src/Web/WebMVC/Views/Order/Create.cshtml
@@ -89,6 +89,7 @@
+
diff --git a/src/Web/WebSPA/Client/guid.ts b/src/Web/WebSPA/Client/guid.ts
new file mode 100644
index 000000000..1904902cb
--- /dev/null
+++ b/src/Web/WebSPA/Client/guid.ts
@@ -0,0 +1,8 @@
+export class Guid {
+ static newGuid() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
+ }
+}
diff --git a/src/Web/WebSPA/Client/modules/orders/orders.service.ts b/src/Web/WebSPA/Client/modules/orders/orders.service.ts
index 9a7b9a34d..24991056a 100644
--- a/src/Web/WebSPA/Client/modules/orders/orders.service.ts
+++ b/src/Web/WebSPA/Client/modules/orders/orders.service.ts
@@ -44,7 +44,7 @@ export class OrdersService {
}
postOrder(item): Observable {
- return this.service.post(this.ordersUrl + '/api/v1/orders/new', item).map((response: Response) => {
+ return this.service.postWithId(this.ordersUrl + '/api/v1/orders/new', item).map((response: Response) => {
return true;
});
}
diff --git a/src/Web/WebSPA/Client/modules/shared/services/data.service.ts b/src/Web/WebSPA/Client/modules/shared/services/data.service.ts
index 975086f70..785728943 100644
--- a/src/Web/WebSPA/Client/modules/shared/services/data.service.ts
+++ b/src/Web/WebSPA/Client/modules/shared/services/data.service.ts
@@ -9,6 +9,7 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { SecurityService } from './security.service';
+import { Guid } from '../../../guid';
@Injectable()
export class DataService {
@@ -28,13 +29,25 @@ export class DataService {
}).catch(this.handleError);
}
+ postWithId(url: string, data: any, params?: any): Observable {
+ return this.doPost(url, data, true, params);
+ }
+
post(url: string, data: any, params?: any): Observable {
+ return this.doPost(url, data, false, params);
+ }
+
+ private doPost(url: string, data: any, needId: boolean, params?: any): Observable {
let options: RequestOptionsArgs = {};
+ options.headers = new Headers();
if (this.securityService) {
- options.headers = new Headers();
options.headers.append('Authorization', 'Bearer ' + this.securityService.GetToken());
}
+ if (needId) {
+ let guid = Guid.newGuid();
+ options.headers.append('x-requestid', guid);
+ }
return this.http.post(url, data, options).map(
(res: Response) => {