Skip to content

Commit

Permalink
fix request binder in order command api (create endpoint) (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
behdad088 authored Nov 10, 2024
1 parent 870d96a commit 28dfd1f
Show file tree
Hide file tree
Showing 14 changed files with 202 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Order.Command.Application.Dtos;
using Order.Command.Application.Orders.Commands.CreateOrder;

namespace Order.Command.API.Endpoints.CreateOrder;
Expand Down Expand Up @@ -29,9 +28,49 @@ public override async Task<IResult> HandleAsync(Request request)

private static CreateOrderCommand? ToCommand(Request? request)
{
return request is null ? null : new CreateOrderCommand(request.Order);
return request is null
? null
: new CreateOrderCommand(
new OrderDto(
Id: request.Id,
CustomerId: request.CustomerId,
OrderName: request.OrderName,
ShippingAddress: MapAddress(request.ShippingAddress),
BillingAddress: MapAddress(request.BillingAddress),
OrderPayment: MapPayment(request.Payment),
OrderItems: request.OrderItems.Select(x =>
new OrderDto.OrderItem(
x.Id,
x.OrderId,
x.ProductId,
x.Quantity,
x.Price
)).ToList()));
}

private static OrderDto.Address MapAddress(AddressDto addressDto)
{
return new OrderDto.Address(
Firstname: addressDto.Firstname,
Lastname: addressDto.Lastname,
EmailAddress: addressDto.EmailAddress,
AddressLine: addressDto.AddressLine,
Country: addressDto.Country,
State: addressDto.State,
ZipCode: addressDto.ZipCode);
}

private static OrderDto.Payment MapPayment(PaymentDto paymentDto)
{
return new OrderDto.Payment(
paymentDto.CardName,
paymentDto.CardNumber,
paymentDto.Expiration,
paymentDto.Cvv,
paymentDto.PaymentMethod);
}


private static Response MapResult(CreateOrderResult result)
{
return new Response(result.Id);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,63 @@
using Order.Command.Application.Dtos;

namespace Order.Command.API.Endpoints.CreateOrder;

public record Request
{
[property: JsonPropertyName("order")] public CreateOrderDto Order { get; set; }
}
[property: JsonPropertyName("id")] public string Id { get; set; }

Check warning on line 5 in src/Services/Order.Command/Order.Command.API/Endpoints/CreateOrder/Models.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Id' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[property: JsonPropertyName("customer_id")]
public string CustomerId { get; set; }

Check warning on line 8 in src/Services/Order.Command/Order.Command.API/Endpoints/CreateOrder/Models.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'CustomerId' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[property: JsonPropertyName("order_name")]
public string OrderName { get; set; }

Check warning on line 11 in src/Services/Order.Command/Order.Command.API/Endpoints/CreateOrder/Models.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'OrderName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[property: JsonPropertyName("shipping_Address")]
public AddressDto ShippingAddress { get; set; }

Check warning on line 14 in src/Services/Order.Command/Order.Command.API/Endpoints/CreateOrder/Models.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'ShippingAddress' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[property: JsonPropertyName("billing_address")]
public AddressDto BillingAddress { get; set; }

Check warning on line 17 in src/Services/Order.Command/Order.Command.API/Endpoints/CreateOrder/Models.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'BillingAddress' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[property: JsonPropertyName("payment")]
public PaymentDto Payment { get; set; }

Check warning on line 20 in src/Services/Order.Command/Order.Command.API/Endpoints/CreateOrder/Models.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Payment' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[property: JsonPropertyName("order_items")]
public List<OrderItemsDto> OrderItems { get; set; }

Check warning on line 23 in src/Services/Order.Command/Order.Command.API/Endpoints/CreateOrder/Models.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'OrderItems' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
};

public record AddressDto(
[property: JsonPropertyName("firstname")]
string Firstname,
[property: JsonPropertyName("lastname")]
string Lastname,
[property: JsonPropertyName("email_address")]
string EmailAddress,
[property: JsonPropertyName("address_line")]
string AddressLine,
[property: JsonPropertyName("country")]
string Country,
[property: JsonPropertyName("state")] string State,
[property: JsonPropertyName("zip_code")]
string ZipCode);

public record PaymentDto(
[property: JsonPropertyName("card_name")]
string CardName,
[property: JsonPropertyName("card_number")]
string CardNumber,
[property: JsonPropertyName("expiration")]
string Expiration,
[property: JsonPropertyName("cvv")] string Cvv,
[property: JsonPropertyName("payment_method")]
int PaymentMethod);

public record OrderItemsDto(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("order_id")]
string OrderId,
[property: JsonPropertyName("product_id")]
string ProductId,
[property: JsonPropertyName("quantity")]
int? Quantity,
[property: JsonPropertyName("price")] decimal? Price);

public record Response(
[property: JsonPropertyName("order_id")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public interface IEndpoint
protected HttpContext Context { get; set; } = default!;
protected CancellationToken CancellationToken { get; set; }

private static JsonSerializerOptions JsonSerializerOptions = new() { PropertyNameCaseInsensitive = true };

/// <summary>
/// Map the endpoint with a route options : <see cref="Post" />, <see cref="Get" />, <see cref="Delete" />,
/// <see cref="Put" />, <see cref="Patch" />
Expand Down Expand Up @@ -126,9 +128,8 @@ private static async Task<TRequest> BindRequestAsync(HttpContext context)
{
var request = new TRequest();
var properties = typeof(TRequest).GetProperties(BindingFlags.Public | BindingFlags.Instance);

request = await BindRequestBody(properties, context, request);
BindQueryAndRoute(properties, context, request);
await BindRequestBody(properties, context, request);
return request;
}

Expand All @@ -152,44 +153,31 @@ private static void BindQueryAndRoute(PropertyInfo[] properties, HttpContext con
}
}

private static async Task BindRequestBody(PropertyInfo[] properties, HttpContext context, TRequest request)
private static async Task<TRequest> BindRequestBody(PropertyInfo[] properties, HttpContext context,
TRequest request)
{
if (context.Request.HasJsonContentType() && context.Request.ContentLength > 0)
if (!context.Request.HasJsonContentType() || !(context.Request.ContentLength > 0)) return new TRequest();

context.Request.EnableBuffering(); // Allow the stream to be read multiple times
context.Request.Body.Position = 0; // Ensure we're at the start of the stream

using var reader = new StreamReader(context.Request.Body);
var jsonBody = await reader.ReadToEndAsync();
context.Request.Body.Position = 0; // Reset stream position after reading

if (string.IsNullOrWhiteSpace(jsonBody))
return new TRequest();

// var jsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
try
{
var bodyData = JsonSerializer.Deserialize<TRequest>(jsonBody, JsonSerializerOptions);
return bodyData ?? new TRequest();
}
catch (JsonException ex)
{
context.Request.EnableBuffering(); // Allow the stream to be read multiple times
context.Request.Body.Position = 0; // Ensure we're at the start of the stream

using var reader = new StreamReader(context.Request.Body);
var jsonBody = await reader.ReadToEndAsync();
context.Request.Body.Position = 0; // Reset stream position after reading

if (string.IsNullOrWhiteSpace(jsonBody))
return;

var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };

try
{
var bodyData = JsonSerializer.Deserialize<TRequest>(jsonBody, options);

if (bodyData != null)
foreach (var property in properties)
{
var jsonName = GetAttributeName<JsonPropertyNameAttribute>(property) ?? property.Name;
var bodyProperty = bodyData.GetType().GetProperty(jsonName,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

if (bodyProperty == null) continue;

var value = bodyProperty.GetValue(bodyData);
if (value != null) property.SetValue(request, value);
}
}
catch (JsonException ex)
{
Console.WriteLine($"JSON Deserialization error: {ex.Message}");
throw;
}
Console.WriteLine($"JSON Deserialization error: {ex.Message}");
throw;
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ public record OrderDto(
PaymentDto Payment,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("order_items")]
List<OrderItemsDto> OrderItems);
List<OrderItems> OrderItems);
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

namespace Order.Command.Application.Dtos;

public record OrderItemsDto(
[property: JsonPropertyName("id")] Ulid Id,
public record OrderItems(
[property: JsonPropertyName("id")]
string Id,
[property: JsonPropertyName("order_id")]
Ulid? OrderId,
string? OrderId,
[property: JsonPropertyName("product_id")]
Ulid? ProductId,
string? ProductId,
[property: JsonPropertyName("quantity")]
int? Quantity,
[property: JsonPropertyName("price")] decimal? Price);
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ public record UpdateOrderDto(
PaymentDto Payment,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("order_items")]
List<OrderItemsDto> OrderItems);
List<OrderItems> OrderItems);
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
namespace Order.Command.Application.Orders.Commands.CreateOrder;

public record CreateOrderCommand(CreateOrderDto Order) : ICommand<CreateOrderResult>;
public record CreateOrderCommand(OrderDto OrderDto) : ICommand<CreateOrderResult>;

public record CreateOrderResult(Ulid Id);

public class CreateOrderCommandHandler(IApplicationDbContext dbContext) : ICommandHandler<CreateOrderCommand, CreateOrderResult>
public class CreateOrderCommandHandler(IApplicationDbContext dbContext)
: ICommandHandler<CreateOrderCommand, CreateOrderResult>
{
public async Task<CreateOrderResult> Handle(CreateOrderCommand command, CancellationToken cancellationToken)
{
var order = MapOrder(command.Order);
var order = MapOrder(command.OrderDto);
dbContext.Orders.Add(order);
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return new CreateOrderResult(order.Id.Value);
}

private static Domain.Models.Order MapOrder(CreateOrderDto orderDto)
private static Domain.Models.Order MapOrder(OrderDto orderDtoDto)
{
var order = new Domain.Models.Order().Create(
id: OrderId.From(orderDto.Id),
customerId: CustomerId.From(orderDto.CustomerId!.Value),
orderName: OrderName.From(orderDto.OrderName),
shippingAddress: MapAddress(orderDto.ShippingAddress),
billingAddress: MapAddress(orderDto.BillingAddress),
payment: MapPayment(orderDto.Payment),
orderItems: orderDto.OrderItems.Select(x =>
id: OrderId.From(Ulid.Parse(orderDtoDto.Id)),
customerId: CustomerId.From(Guid.Parse(orderDtoDto.CustomerId!)),
orderName: OrderName.From(orderDtoDto.OrderName),
shippingAddress: MapAddress(orderDtoDto.ShippingAddress),
billingAddress: MapAddress(orderDtoDto.BillingAddress),
payment: MapPayment(orderDtoDto.OrderPayment),
orderItems: orderDtoDto.OrderItems.Select(x =>
new OrderItem(
orderId: OrderId.From(orderDto.Id),
productId: ProductId.From(x.ProductId!.Value),
orderId: OrderId.From(Ulid.Parse(orderDtoDto.Id)),
productId: ProductId.From(Ulid.Parse(x.ProductId!)),
quantity: x.Quantity!.Value,
price: Price.From(x.Price!.Value))).ToList());

return order;
}

private static Address MapAddress(AddressDto addressDto)
private static Address MapAddress(OrderDto.Address addressDto)
=> new(
firstName: addressDto.Firstname,
lastName: addressDto.Lastname,
Expand All @@ -42,8 +44,8 @@ private static Address MapAddress(AddressDto addressDto)
state: addressDto.State,
zipCode: addressDto.ZipCode);

private static Payment MapPayment(PaymentDto paymentDto) =>
new (
private static Payment MapPayment(OrderDto.Payment paymentDto) =>
new(
cardName: paymentDto.CardName,
cardNumber: paymentDto.CardName,
expiration: paymentDto.Expiration,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace Order.Command.Application.Orders.Commands.CreateOrder;

public record OrderDto(
string Id,
string? CustomerId,
string OrderName,
OrderDto.Address ShippingAddress,
OrderDto.Address BillingAddress,
OrderDto.Payment OrderPayment,
List<OrderDto.OrderItem> OrderItems)
{
public record Address(
string Firstname,
string Lastname,
string EmailAddress,
string AddressLine,
string Country,
string State,
string ZipCode);

public record Payment(
string CardName,
string CardNumber,
string Expiration,
string Cvv,
int PaymentMethod);

public record OrderItem(
string Id,
string? OrderId,
string? ProductId,
int? Quantity,
decimal? Price);
}





Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
public CreateOrderCommandValidator()
{
RuleFor(x => x.Order.OrderName).NotEmpty().WithMessage("Name is required.");
RuleFor(x => x.Order.CustomerId).NotNull().WithMessage("CustomerId is required.");
RuleFor(x => x.Order.OrderItems).NotEmpty().WithMessage("OrderItems should not be empty.");
RuleFor(x => x.OrderDto.OrderName).NotEmpty().WithMessage("Name is required.");
RuleFor(x => x.OrderDto.CustomerId).NotNull().WithMessage("CustomerId is required.");
RuleFor(x => x.OrderDto.OrderItems).NotEmpty().WithMessage("OrderItems should not be empty.");

RuleForEach(x => x.Order.OrderItems).SetValidator(new OrderItemValidator());
RuleForEach(x => x.OrderDto.OrderItems).SetValidator(new OrderItemValidator());
}

private class OrderItemValidator : AbstractValidator<OrderItemsDto>
private class OrderItemValidator : AbstractValidator<OrderDto.OrderItem>
{
public OrderItemValidator()
{
Expand Down
Loading

0 comments on commit 28dfd1f

Please sign in to comment.