Browse Source

Idempotent updates based on requestid

pull/73/head
etomas 8 years ago
parent
commit
f9b15481d1
27 changed files with 536 additions and 65 deletions
  1. +9
    -0
      docker-compose-external.override.yml
  2. +10
    -0
      docker-compose-external.yml
  3. +0
    -50
      eShopOnContainers-ServicesAndWebApps.sln
  4. +1
    -1
      src/Services/Catalog/Catalog.API/Catalog.API.csproj
  5. +2
    -1
      src/Services/Catalog/Catalog.API/Startup.cs
  6. +1
    -1
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs
  7. +14
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
  8. +20
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommand.cs
  9. +60
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/IdentifierCommandHandler.cs
  10. +7
    -2
      src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
  11. +4
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/ApplicationModule.cs
  12. +245
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.Designer.cs
  13. +33
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.cs
  14. +15
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs
  15. +2
    -1
      src/Services/Ordering/Ordering.API/Ordering.API.csproj
  16. +4
    -4
      src/Services/Ordering/Ordering.API/Startup.cs
  17. +1
    -1
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  18. +13
    -0
      src/Services/Ordering/Ordering.Infrastructure/ClientRequest.cs
  19. +10
    -0
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  20. +13
    -0
      src/Services/Ordering/Ordering.Infrastructure/Repositories/IRequestManager.cs
  21. +42
    -0
      src/Services/Ordering/Ordering.Infrastructure/Repositories/RequestManager.cs
  22. +4
    -0
      src/Web/WebMVC/Models/Order.cs
  23. +2
    -2
      src/Web/WebMVC/Services/OrderingService.cs
  24. +1
    -0
      src/Web/WebMVC/Views/Order/Create.cshtml
  25. +8
    -0
      src/Web/WebSPA/Client/guid.ts
  26. +1
    -1
      src/Web/WebSPA/Client/modules/orders/orders.service.ts
  27. +14
    -1
      src/Web/WebSPA/Client/modules/shared/services/data.service.ts

+ 9
- 0
docker-compose-external.override.yml View File

@ -0,0 +1,9 @@
version: '2'
services:
sql.data:
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5433:1433"

+ 10
- 0
docker-compose-external.yml View File

@ -0,0 +1,10 @@
version: '2'
services:
sql.data:
image: microsoft/mssql-server-linux
basket.data:
image: redis
ports:
- "6379:6379"

+ 0
- 50
eShopOnContainers-ServicesAndWebApps.sln View File

@ -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


+ 1
- 1
src/Services/Catalog/Catalog.API/Catalog.API.csproj View File

@ -49,7 +49,7 @@
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0-msbuild3-final" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup>
<ItemGroup>


+ 2
- 1
src/Services/Catalog/Catalog.API/Startup.cs View File

@ -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();


+ 1
- 1
src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs View File

@ -64,7 +64,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
_orderItems = new List<OrderItemDTO>();
}
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()
{


+ 14
- 0
src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs View File

@ -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<CreateOrderCommand, bool>
{
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<CreateOrderCommand, bool>
{


+ 20
- 0
src/Services/Ordering/Ordering.API/Application/Commands/IdentifiedCommand.cs View File

@ -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<T, R> : IAsyncRequest<R>
where T : IAsyncRequest<R>
{
public T Command { get; }
public Guid Id { get; }
public IdentifiedCommand(T command, Guid id)
{
Command = command;
Id = id;
}
}
}

+ 60
- 0
src/Services/Ordering/Ordering.API/Application/Commands/IdentifierCommandHandler.cs View File

@ -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
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">Type of the command handler that performs the operation if request is not duplicated</typeparam>
/// <typeparam name="R">Return value of the inner command handler</typeparam>
public class IdentifierCommandHandler<T, R> : IAsyncRequestHandler<IdentifiedCommand<T, R>, R>
where T : IAsyncRequest<R>
{
private readonly IMediator _mediator;
private readonly IRequestManager _requestManager;
public IdentifierCommandHandler(IMediator mediator, IRequestManager requestManager)
{
_mediator = mediator;
_requestManager = requestManager;
}
/// <summary>
/// Creates the result value to return if a previous request was found
/// </summary>
/// <returns></returns>
protected virtual R CreateResultForDuplicateRequest()
{
return default(R);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="message">IdentifiedCommand which contains both original command & request ID</param>
/// <returns>Return value of inner command or default value if request same ID was found</returns>
public async Task<R> Handle(IdentifiedCommand<T, R> message)
{
var alreadyExists = await _requestManager.ExistAsync(message.Id);
if (alreadyExists)
{
return CreateResultForDuplicateRequest();
}
else
{
await _requestManager.CreateRequestForCommandAsync<T>(message.Id);
var result = await _mediator.SendAsync(message.Command);
return result;
}
}
}
}

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

@ -28,9 +28,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[Route("new")]
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand createOrderCommand)
public async Task<IActionResult> 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, bool>(createOrderCommand, guid);
result = await _mediator.SendAsync(requestCreateOrder);
}
if (result)
{
return Ok();


+ 4
- 0
src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/ApplicationModule.cs View File

@ -33,6 +33,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
builder.RegisterType<OrderRepository>()
.As<IOrderRepository<Order>>()
.InstancePerLifetimeScope();
builder.RegisterType<RequestManager>()
.As<IRequestManager>()
.InstancePerLifetimeScope();
}
}
}

+ 245
- 0
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.Designer.cs View File

@ -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<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("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<int>("Id")
.HasDefaultValue(1);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.ToTable("cardtypes","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Alias")
.IsRequired()
.HasMaxLength(200);
b.Property<int>("BuyerId");
b.Property<string>("CardHolderName")
.IsRequired()
.HasMaxLength(200);
b.Property<string>("CardNumber")
.IsRequired()
.HasMaxLength(25);
b.Property<int>("CardTypeId");
b.Property<DateTime>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("City");
b.Property<string>("Country");
b.Property<string>("State");
b.Property<string>("Street");
b.Property<string>("ZipCode");
b.HasKey("Id");
b.ToTable("address","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "orderseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int?>("AddressId");
b.Property<int>("BuyerId");
b.Property<DateTime>("OrderDate");
b.Property<int>("OrderStatusId");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<decimal>("Discount");
b.Property<int>("OrderId");
b.Property<string>("PictureUrl");
b.Property<int>("ProductId");
b.Property<string>("ProductName")
.IsRequired();
b.Property<decimal>("UnitPrice");
b.Property<int>("Units");
b.HasKey("Id");
b.HasIndex("OrderId");
b.ToTable("orderItems","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b =>
{
b.Property<int>("Id")
.HasDefaultValue(1);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.ToTable("orderstatus","ordering");
});
modelBuilder.Entity("Ordering.Infrastructure.ClientRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired();
b.Property<DateTime>("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);
});
}
}
}

+ 33
- 0
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.cs View File

@ -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<Guid>(nullable: false),
Name = table.Column<string>(nullable: false),
Time = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_requests", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "requests",
schema: "ordering");
}
}
}

+ 15
- 0
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs View File

@ -183,6 +183,21 @@ namespace Ordering.API.Migrations
b.ToTable("orderstatus","ordering");
});
modelBuilder.Entity("Ordering.Infrastructure.ClientRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired();
b.Property<DateTime>("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")


+ 2
- 1
src/Services/Ordering/Ordering.API/Ordering.API.csproj View File

@ -51,10 +51,11 @@
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.0.1-rc3" />
<PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.1.0-preview4-final" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup>
<ItemGroup>


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

@ -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<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IIdentityService,IdentityService>();
services.AddTransient<IIdentityService, IdentityService>();
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());
}


+ 1
- 1
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

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


+ 13
- 0
src/Services/Ordering/Ordering.Infrastructure/ClientRequest.cs View File

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

+ 10
- 0
src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs View File

@ -30,6 +30,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ClientRequest>(ConfigureRequests);
modelBuilder.Entity<Address>(ConfigureAddress);
modelBuilder.Entity<PaymentMethod>(ConfigurePayment);
modelBuilder.Entity<Order>(ConfigureOrder);
@ -39,6 +41,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
modelBuilder.Entity<Buyer>(ConfigureBuyer);
}
private void ConfigureRequests(EntityTypeBuilder<ClientRequest> 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<Address> addressConfiguration)
{
addressConfiguration.ToTable("address", DEFAULT_SCHEMA);


+ 13
- 0
src/Services/Ordering/Ordering.Infrastructure/Repositories/IRequestManager.cs View File

@ -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<bool> ExistAsync(Guid id);
Task CreateRequestForCommandAsync<T>(Guid id);
}
}

+ 42
- 0
src/Services/Ordering/Ordering.Infrastructure/Repositories/RequestManager.cs View File

@ -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<bool> ExistAsync(Guid id)
{
var request = await _context.FindAsync<ClientRequest>(id);
return request != null;
}
public async Task CreateRequestForCommandAsync<T>(Guid id)
{
var exists = await ExistAsync(id);
var request = exists ?
throw new Exception($"Request with {id} already exists") :
new ClientRequest()
{
Id = id,
Name = typeof(T).Name,
Time = DateTime.UtcNow
};
_context.Add(request);
await _context.SaveChangesAsync();
}
}
}

+ 4
- 0
src/Web/WebMVC/Models/Order.cs View File

@ -55,6 +55,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Models
public List<OrderItem> OrderItems { get; }
[Required]
public Guid RequestId { get; set; }
public void CardExpirationShortFormat()
{
CardExpirationShort = CardExpiration.ToString("MM/yy");


+ 2
- 2
src/Web/WebMVC/Services/OrderingService.cs View File

@ -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)


+ 1
- 0
src/Web/WebMVC/Views/Order/Create.cshtml View File

@ -89,6 +89,7 @@
</div>
</section>
<input asp-for="ZipCode" type="hidden" />
<input asp-for="RequestId" type="hidden" value="@Guid.NewGuid().ToString()"/>
</form>
</div>


+ 8
- 0
src/Web/WebSPA/Client/guid.ts View File

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

+ 1
- 1
src/Web/WebSPA/Client/modules/orders/orders.service.ts View File

@ -44,7 +44,7 @@ export class OrdersService {
}
postOrder(item): Observable<boolean> {
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;
});
}


+ 14
- 1
src/Web/WebSPA/Client/modules/shared/services/data.service.ts View File

@ -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<Response> {
return this.doPost(url, data, true, params);
}
post(url: string, data: any, params?: any): Observable<Response> {
return this.doPost(url, data, false, params);
}
private doPost(url: string, data: any, needId: boolean, params?: any): Observable<Response> {
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) => {


Loading…
Cancel
Save