Merge branch 'idempotent-command'
This commit is contained in:
commit
b1b3abfcff
9
docker-compose-external.override.yml
Normal file
9
docker-compose-external.override.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
sql.data:
|
||||||
|
environment:
|
||||||
|
- SA_PASSWORD=Pass@word
|
||||||
|
- ACCEPT_EULA=Y
|
||||||
|
ports:
|
||||||
|
- "5433:1433"
|
10
docker-compose-external.yml
Normal file
10
docker-compose-external.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
sql.data:
|
||||||
|
image: microsoft/mssql-server-linux
|
||||||
|
|
||||||
|
basket.data:
|
||||||
|
image: redis
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
@ -49,7 +49,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -28,7 +29,7 @@
|
|||||||
|
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
builder.AddUserSecrets();
|
builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AddEnvironmentVariables();
|
builder.AddEnvironmentVariables();
|
||||||
|
@ -64,7 +64,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
|
|||||||
_orderItems = new List<OrderItemDTO>();
|
_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 cardNumber, string cardHolderName, DateTime cardExpiration,
|
||||||
string cardSecurityNumber, int cardTypeId) : this()
|
string cardSecurityNumber, int cardTypeId) : this()
|
||||||
{
|
{
|
||||||
|
@ -4,9 +4,23 @@
|
|||||||
using Domain.AggregatesModel.OrderAggregate;
|
using Domain.AggregatesModel.OrderAggregate;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
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
|
public class CreateOrderCommandHandler
|
||||||
: IAsyncRequestHandler<CreateOrderCommand, bool>
|
: IAsyncRequestHandler<CreateOrderCommand, bool>
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -28,9 +28,21 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
|
|||||||
|
|
||||||
[Route("new")]
|
[Route("new")]
|
||||||
[HttpPost]
|
[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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If no x-requestid header is found we process the order anyway. This is just temporary to not break existing clients
|
||||||
|
// that aren't still updated. When all clients were updated this could be removed.
|
||||||
|
result = await _mediator.SendAsync(createOrderCommand);
|
||||||
|
}
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
return Ok();
|
return Ok();
|
||||||
|
@ -33,6 +33,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
|
|||||||
builder.RegisterType<OrderRepository>()
|
builder.RegisterType<OrderRepository>()
|
||||||
.As<IOrderRepository<Order>>()
|
.As<IOrderRepository<Order>>()
|
||||||
.InstancePerLifetimeScope();
|
.InstancePerLifetimeScope();
|
||||||
|
|
||||||
|
builder.RegisterType<RequestManager>()
|
||||||
|
.As<IRequestManager>()
|
||||||
|
.InstancePerLifetimeScope();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
245
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.Designer.cs
generated
Normal file
245
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.Designer.cs
generated
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -183,6 +183,21 @@ namespace Ordering.API.Migrations
|
|||||||
b.ToTable("orderstatus","ordering");
|
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 =>
|
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
|
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
|
||||||
|
@ -51,10 +51,11 @@
|
|||||||
<PackageReference Include="System.Reflection" Version="4.3.0" />
|
<PackageReference Include="System.Reflection" Version="4.3.0" />
|
||||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.0.1-rc3" />
|
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.0.1-rc3" />
|
||||||
<PackageReference Include="Dapper" Version="1.50.2" />
|
<PackageReference Include="Dapper" Version="1.50.2" />
|
||||||
|
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
builder.AddUserSecrets();
|
builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AddEnvironmentVariables();
|
builder.AddEnvironmentVariables();
|
||||||
@ -67,7 +67,7 @@
|
|||||||
Title = "Ordering HTTP API",
|
Title = "Ordering HTTP API",
|
||||||
Version = "v1",
|
Version = "v1",
|
||||||
Description = "The Ordering Service HTTP API",
|
Description = "The Ordering Service HTTP API",
|
||||||
TermsOfService = "Terms Of Service"
|
TermsOfService = "Terms Of Service"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -82,7 +82,7 @@
|
|||||||
|
|
||||||
// Add application services.
|
// Add application services.
|
||||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
services.AddTransient<IIdentityService,IdentityService>();
|
services.AddTransient<IIdentityService, IdentityService>();
|
||||||
|
|
||||||
services.AddOptions();
|
services.AddOptions();
|
||||||
|
|
||||||
@ -92,7 +92,7 @@
|
|||||||
container.Populate(services);
|
container.Populate(services);
|
||||||
|
|
||||||
container.RegisterModule(new MediatorModule());
|
container.RegisterModule(new MediatorModule());
|
||||||
container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"] ));
|
container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"]));
|
||||||
|
|
||||||
return new AutofacServiceProvider(container.Build());
|
return new AutofacServiceProvider(container.Build());
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
|
|||||||
public OrderStatus OrderStatus { get; private set; }
|
public OrderStatus OrderStatus { get; private set; }
|
||||||
private int _orderStatusId;
|
private int _orderStatusId;
|
||||||
|
|
||||||
|
|
||||||
// DDD Patterns comment
|
// DDD Patterns comment
|
||||||
// Using a private collection field, better for DDD Aggregate's encapsulation
|
// Using a private collection field, better for DDD Aggregate's encapsulation
|
||||||
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
|
// 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;
|
_paymentMethodId = paymentMethodId;
|
||||||
_orderStatusId = OrderStatus.InProcess.Id;
|
_orderStatusId = OrderStatus.InProcess.Id;
|
||||||
_orderDate = DateTime.UtcNow;
|
_orderDate = DateTime.UtcNow;
|
||||||
|
|
||||||
Address = address;
|
Address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
|
|||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
modelBuilder.Entity<ClientRequest>(ConfigureRequests);
|
||||||
modelBuilder.Entity<Address>(ConfigureAddress);
|
modelBuilder.Entity<Address>(ConfigureAddress);
|
||||||
modelBuilder.Entity<PaymentMethod>(ConfigurePayment);
|
modelBuilder.Entity<PaymentMethod>(ConfigurePayment);
|
||||||
modelBuilder.Entity<Order>(ConfigureOrder);
|
modelBuilder.Entity<Order>(ConfigureOrder);
|
||||||
@ -39,6 +41,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
|
|||||||
modelBuilder.Entity<Buyer>(ConfigureBuyer);
|
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)
|
void ConfigureAddress(EntityTypeBuilder<Address> addressConfiguration)
|
||||||
{
|
{
|
||||||
addressConfiguration.ToTable("address", DEFAULT_SCHEMA);
|
addressConfiguration.ToTable("address", DEFAULT_SCHEMA);
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -55,6 +55,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Models
|
|||||||
|
|
||||||
public List<OrderItem> OrderItems { get; }
|
public List<OrderItem> OrderItems { get; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public Guid RequestId { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public void CardExpirationShortFormat()
|
public void CardExpirationShortFormat()
|
||||||
{
|
{
|
||||||
CardExpirationShort = CardExpiration.ToString("MM/yy");
|
CardExpirationShort = CardExpiration.ToString("MM/yy");
|
||||||
|
@ -79,14 +79,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
|||||||
|
|
||||||
_apiClient = new HttpClient();
|
_apiClient = new HttpClient();
|
||||||
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
_apiClient.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString());
|
||||||
var ordersUrl = $"{_remoteServiceBaseUrl}/new";
|
var ordersUrl = $"{_remoteServiceBaseUrl}/new";
|
||||||
order.CardTypeId = 1;
|
order.CardTypeId = 1;
|
||||||
order.CardExpirationApiFormat();
|
order.CardExpirationApiFormat();
|
||||||
SetFakeIdToProducts(order);
|
SetFakeIdToProducts(order);
|
||||||
|
|
||||||
StringContent content = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
|
StringContent content = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
var response = await _apiClient.PostAsync(ordersUrl, content);
|
var response = await _apiClient.PostAsync(ordersUrl, content);
|
||||||
|
|
||||||
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
|
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
|
||||||
|
@ -89,6 +89,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<input asp-for="ZipCode" type="hidden" />
|
<input asp-for="ZipCode" type="hidden" />
|
||||||
|
<input asp-for="RequestId" type="hidden" value="@Guid.NewGuid().ToString()"/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
8
src/Web/WebSPA/Client/guid.ts
Normal file
8
src/Web/WebSPA/Client/guid.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,7 @@ export class OrdersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
postOrder(item): Observable<boolean> {
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import 'rxjs/add/operator/map';
|
|||||||
import 'rxjs/add/operator/catch';
|
import 'rxjs/add/operator/catch';
|
||||||
|
|
||||||
import { SecurityService } from './security.service';
|
import { SecurityService } from './security.service';
|
||||||
|
import { Guid } from '../../../guid';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DataService {
|
export class DataService {
|
||||||
@ -28,13 +29,25 @@ export class DataService {
|
|||||||
}).catch(this.handleError);
|
}).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> {
|
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 = {};
|
let options: RequestOptionsArgs = {};
|
||||||
|
|
||||||
|
options.headers = new Headers();
|
||||||
if (this.securityService) {
|
if (this.securityService) {
|
||||||
options.headers = new Headers();
|
|
||||||
options.headers.append('Authorization', 'Bearer ' + this.securityService.GetToken());
|
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(
|
return this.http.post(url, data, options).map(
|
||||||
(res: Response) => {
|
(res: Response) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user