Skip to content

Commit

Permalink
Mediatr example (dotnet-architecture#325)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
efleming18 authored and ardalis committed Nov 15, 2019
1 parent 1bae9e6 commit 29d1497
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 67 deletions.
60 changes: 12 additions & 48 deletions src/Web/Controllers/OrderController.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/Web/Features/MyOrders/GetMyOrders.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 43 additions & 0 deletions src/Web/Features/MyOrders/GetMyOrdersHandler.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 17 additions & 0 deletions src/Web/Features/OrderDetails/GetOrderDetails.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 47 additions & 0 deletions src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs
Original file line number Diff line number Diff line change
@@ -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()
};
}
}
}
15 changes: 8 additions & 7 deletions src/Web/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Ardalis.ListStartupServices;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
Expand Down Expand Up @@ -81,7 +82,9 @@ public void ConfigureServices(IServiceCollection services)
ConfigureCookieSettings(services);

CreateIdentityIfNotCreated(services);


services.AddMediatR(typeof(BasketViewModelService).Assembly);

services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
services.AddScoped<ICatalogViewModelService, CachedCatalogViewModelService>();
services.AddScoped<IBasketService, BasketService>();
Expand Down Expand Up @@ -109,13 +112,12 @@ public void ConfigureServices(IServiceCollection services)
options.Conventions.Add(new RouteTokenTransformerConvention(
new SlugifyParameterTransformer()));

});
services.AddControllersWithViews();
});
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/Basket/Checkout");
});

services.AddControllersWithViews();
services.AddControllers();

services.AddHttpContextAccessor();
Expand Down Expand Up @@ -207,14 +209,13 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
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();

Expand Down
7 changes: 1 addition & 6 deletions src/Web/ViewModels/OrderItemViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
10 changes: 4 additions & 6 deletions src/Web/ViewModels/OrderViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 Address ShippingAddress { get; set; }

public string Status => DEFAULT_STATUS;
public Address ShippingAddress { get; set; }
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>();

}

}
3 changes: 3 additions & 0 deletions src/Web/Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
38 changes: 38 additions & 0 deletions tests/UnitTests/MediatorHandlers/OrdersTests/GetMyOrders_Should.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading

0 comments on commit 29d1497

Please sign in to comment.