Mediatr example (#325)
* Updates based on documentation * Getting the build passing * Getting app functioning * A few cleanups to confirm it's working as expected * Fixing functional tests * Updating dockerfile for 3.0 * Functional Tests now run sequentially * Updating to latest version of moq * Adding migration for post 3.0 upgrades * Removing commented out lines * Moving address and catalogitemordered configuration in to classes that own them * Installing MediatR nuget packages * Configure MediatR in Startup * Creating GetMyOrders MediatR query and handler * Adding GetOrderDetails MediatR handler * Refactoring out default values * Added tests for GetOrderDetails mediator handler * Defaulting values on Models for now * Removing some spaces * Splitting files * Splitting out the GetOrderDetails files * Adding test for GetMyOrders * restructuing folders * Using constant
This commit is contained in:
committed by
Steve Smith
parent
1bae9e68d9
commit
29d1497a3f
@@ -1,9 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Linq;
|
||||
using Microsoft.eShopWeb.Web.Features.MyOrders;
|
||||
using Microsoft.eShopWeb.Web.Features.OrderDetails;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Controllers
|
||||
@@ -13,66 +12,31 @@ namespace Microsoft.eShopWeb.Web.Controllers
|
||||
[Route("[controller]/[action]")]
|
||||
public class OrderController : Controller
|
||||
{
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public OrderController(IOrderRepository orderRepository)
|
||||
public OrderController(IMediator mediator)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
[HttpGet()]
|
||||
public async Task<IActionResult> MyOrders()
|
||||
{
|
||||
var orders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(User.Identity.Name));
|
||||
var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));
|
||||
|
||||
var viewModel = orders
|
||||
.Select(o => new OrderViewModel()
|
||||
{
|
||||
OrderDate = o.OrderDate,
|
||||
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
|
||||
{
|
||||
Discount = 0,
|
||||
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||
ProductName = oi.ItemOrdered.ProductName,
|
||||
UnitPrice = oi.UnitPrice,
|
||||
Units = oi.Units
|
||||
}).ToList(),
|
||||
OrderNumber = o.Id,
|
||||
ShippingAddress = o.ShipToAddress,
|
||||
Status = "Pending",
|
||||
Total = o.Total()
|
||||
|
||||
});
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[HttpGet("{orderId}")]
|
||||
public async Task<IActionResult> Detail(int orderId)
|
||||
{
|
||||
var customerOrders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(User.Identity.Name));
|
||||
var order = customerOrders.FirstOrDefault(o => o.Id == orderId);
|
||||
if (order == null)
|
||||
var viewModel = await _mediator.Send(new GetOrderDetails(User.Identity.Name, orderId));
|
||||
|
||||
if (viewModel == null)
|
||||
{
|
||||
return BadRequest("No such order found for this user.");
|
||||
}
|
||||
var viewModel = new OrderViewModel()
|
||||
{
|
||||
OrderDate = order.OrderDate,
|
||||
OrderItems = order.OrderItems.Select(oi => new OrderItemViewModel()
|
||||
{
|
||||
Discount = 0,
|
||||
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||
ProductName = oi.ItemOrdered.ProductName,
|
||||
UnitPrice = oi.UnitPrice,
|
||||
Units = oi.Units
|
||||
}).ToList(),
|
||||
OrderNumber = order.Id,
|
||||
ShippingAddress = order.ShipToAddress,
|
||||
Status = "Pending",
|
||||
Total = order.Total()
|
||||
};
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
16
src/Web/Features/MyOrders/GetMyOrders.cs
Normal file
16
src/Web/Features/MyOrders/GetMyOrders.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using MediatR;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Features.MyOrders
|
||||
{
|
||||
public class GetMyOrders : IRequest<IEnumerable<OrderViewModel>>
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
|
||||
public GetMyOrders(string userName)
|
||||
{
|
||||
UserName = userName;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/Web/Features/MyOrders/GetMyOrdersHandler.cs
Normal file
43
src/Web/Features/MyOrders/GetMyOrdersHandler.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using MediatR;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Features.MyOrders
|
||||
{
|
||||
public class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<OrderViewModel>>
|
||||
{
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
|
||||
public GetMyOrdersHandler(IOrderRepository orderRepository)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OrderViewModel>> Handle(GetMyOrders request, CancellationToken cancellationToken)
|
||||
{
|
||||
var specification = new CustomerOrdersWithItemsSpecification(request.UserName);
|
||||
var orders = await _orderRepository.ListAsync(specification);
|
||||
|
||||
return orders.Select(o => new OrderViewModel
|
||||
{
|
||||
OrderDate = o.OrderDate,
|
||||
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
|
||||
{
|
||||
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||
ProductName = oi.ItemOrdered.ProductName,
|
||||
UnitPrice = oi.UnitPrice,
|
||||
Units = oi.Units
|
||||
}).ToList(),
|
||||
OrderNumber = o.Id,
|
||||
ShippingAddress = o.ShipToAddress,
|
||||
Total = o.Total()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/Web/Features/OrderDetails/GetOrderDetails.cs
Normal file
17
src/Web/Features/OrderDetails/GetOrderDetails.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using MediatR;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Features.OrderDetails
|
||||
{
|
||||
public class GetOrderDetails : IRequest<OrderViewModel>
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public int OrderId { get; set; }
|
||||
|
||||
public GetOrderDetails(string userName, int orderId)
|
||||
{
|
||||
UserName = userName;
|
||||
OrderId = orderId;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs
Normal file
47
src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using MediatR;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Features.OrderDetails
|
||||
{
|
||||
public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderViewModel>
|
||||
{
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
|
||||
public GetOrderDetailsHandler(IOrderRepository orderRepository)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
public async Task<OrderViewModel> Handle(GetOrderDetails request, CancellationToken cancellationToken)
|
||||
{
|
||||
var customerOrders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(request.UserName));
|
||||
var order = customerOrders.FirstOrDefault(o => o.Id == request.OrderId);
|
||||
|
||||
if (order == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OrderViewModel
|
||||
{
|
||||
OrderDate = order.OrderDate,
|
||||
OrderItems = order.OrderItems.Select(oi => new OrderItemViewModel
|
||||
{
|
||||
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||
ProductName = oi.ItemOrdered.ProductName,
|
||||
UnitPrice = oi.UnitPrice,
|
||||
Units = oi.Units
|
||||
}).ToList(),
|
||||
OrderNumber = order.Id,
|
||||
ShippingAddress = order.ShipToAddress,
|
||||
Total = order.Total()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ardalis.ListStartupServices;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@@ -82,6 +83,8 @@ namespace Microsoft.eShopWeb.Web
|
||||
|
||||
CreateIdentityIfNotCreated(services);
|
||||
|
||||
services.AddMediatR(typeof(BasketViewModelService).Assembly);
|
||||
|
||||
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
|
||||
services.AddScoped<ICatalogViewModelService, CachedCatalogViewModelService>();
|
||||
services.AddScoped<IBasketService, BasketService>();
|
||||
@@ -110,12 +113,11 @@ namespace Microsoft.eShopWeb.Web
|
||||
new SlugifyParameterTransformer()));
|
||||
|
||||
});
|
||||
services.AddControllersWithViews();
|
||||
services.AddRazorPages(options =>
|
||||
{
|
||||
options.Conventions.AuthorizePage("/Basket/Checkout");
|
||||
});
|
||||
|
||||
services.AddControllersWithViews();
|
||||
services.AddControllers();
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
@@ -207,14 +209,13 @@ namespace Microsoft.eShopWeb.Web
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCookiePolicy();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
// Enable middleware to serve generated Swagger as a JSON endpoint.
|
||||
app.UseSwagger();
|
||||
|
||||
|
||||
@@ -3,15 +3,10 @@
|
||||
public class OrderItemViewModel
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public decimal Discount { get; set; }
|
||||
|
||||
public decimal Discount => 0;
|
||||
public int Units { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,13 @@ namespace Microsoft.eShopWeb.Web.ViewModels
|
||||
{
|
||||
public class OrderViewModel
|
||||
{
|
||||
private const string DEFAULT_STATUS = "Pending";
|
||||
|
||||
public int OrderNumber { get; set; }
|
||||
public DateTimeOffset OrderDate { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
public string Status { get; set; }
|
||||
|
||||
public string Status => DEFAULT_STATUS;
|
||||
public Address ShippingAddress { get; set; }
|
||||
|
||||
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ardalis.ListStartupServices" Version="1.1.3" />
|
||||
|
||||
<PackageReference Include="MediatR" Version="7.0.0" />
|
||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="7.0.0" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="2.9.406" Condition="'$(Configuration)'=='Release'" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.3.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.0.0" />
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.Web.Features.MyOrders;
|
||||
using Moq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.eShopWeb.UnitTests.MediatorHandlers.OrdersTests
|
||||
{
|
||||
public class GetMyOrders_Should
|
||||
{
|
||||
private readonly Mock<IOrderRepository> _mockOrderRepository;
|
||||
|
||||
public GetMyOrders_Should()
|
||||
{
|
||||
var item = new OrderItem(new CatalogItemOrdered(1, "ProductName", "URI"), 10.00m, 10);
|
||||
var address = new Address(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>());
|
||||
Order order = new Order("buyerId", address, new List<OrderItem> { item });
|
||||
|
||||
_mockOrderRepository = new Mock<IOrderRepository>();
|
||||
_mockOrderRepository.Setup(x => x.ListAsync(It.IsAny<ISpecification<Order>>())).ReturnsAsync(new List<Order> { order });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotReturnNull_If_OrdersArePresent()
|
||||
{
|
||||
var request = new GetMyOrders("SomeUserName");
|
||||
|
||||
var handler = new GetMyOrdersHandler(_mockOrderRepository.Object);
|
||||
|
||||
var result = await handler.Handle(request, CancellationToken.None);
|
||||
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.Web.Features.OrderDetails;
|
||||
using Moq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.eShopWeb.UnitTests.MediatorHandlers.OrdersTests
|
||||
{
|
||||
public class GetOrderDetails_Should
|
||||
{
|
||||
private readonly Mock<IOrderRepository> _mockOrderRepository;
|
||||
|
||||
public GetOrderDetails_Should()
|
||||
{
|
||||
var item = new OrderItem(new CatalogItemOrdered(1, "ProductName", "URI"), 10.00m, 10);
|
||||
var address = new Address(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>());
|
||||
Order order = new Order("buyerId", address, new List<OrderItem> { item });
|
||||
|
||||
_mockOrderRepository = new Mock<IOrderRepository>();
|
||||
_mockOrderRepository.Setup(x => x.ListAsync(It.IsAny<ISpecification<Order>>())).ReturnsAsync(new List<Order> { order });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotBeNull_If_Order_Exists()
|
||||
{
|
||||
var request = new GetOrderDetails("SomeUserName", 0);
|
||||
|
||||
var handler = new GetOrderDetailsHandler(_mockOrderRepository.Object);
|
||||
|
||||
var result = await handler.Handle(request, CancellationToken.None);
|
||||
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BeNull_If_Order_Not_found()
|
||||
{
|
||||
var request = new GetOrderDetails("SomeUserName", 100);
|
||||
|
||||
var handler = new GetOrderDetailsHandler(_mockOrderRepository.Object);
|
||||
|
||||
var result = await handler.Handle(request, CancellationToken.None);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\ApplicationCore\ApplicationCore.csproj" />
|
||||
<ProjectReference Include="..\..\src\Web\Web.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user