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>
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0-msbuild3-final" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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>
|
||||
{
|
||||
|
@ -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")]
|
||||
[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)
|
||||
{
|
||||
return Ok();
|
||||
|
@ -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
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");
|
||||
});
|
||||
|
||||
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")
|
||||
|
@ -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>
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
||||
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);
|
||||
|
@ -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; }
|
||||
|
||||
[Required]
|
||||
public Guid RequestId { get; set; }
|
||||
|
||||
|
||||
public void CardExpirationShortFormat()
|
||||
{
|
||||
CardExpirationShort = CardExpiration.ToString("MM/yy");
|
||||
|
@ -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)
|
||||
|
@ -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
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> {
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
@ -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…
x
Reference in New Issue
Block a user