├── .gitignore ├── LICENSE ├── Part1a ├── OnlineStore.Api │ ├── Controllers │ │ ├── CheckoutController.cs │ │ ├── HomeController.cs │ │ ├── ProductController.cs │ │ └── ShoppingCartController.cs │ ├── Extensions │ │ └── ControllerExtensions.cs │ ├── OnlineStore.Api.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── OnlineStore.DataAccess │ ├── IOrderRepository.cs │ ├── IServiceCollectionExtensions.cs │ ├── IShoppingCartRepository.cs │ ├── IStoreProductRepository.cs │ ├── Implementation │ │ ├── OrderRepository.cs │ │ ├── ShoppingCartRepository.cs │ │ └── StoreProductRepository.cs │ ├── OnlineStore.DataAccess.csproj │ └── RepositoryException.cs ├── OnlineStore.Model │ ├── OnlineStore.Model.csproj │ ├── Order.cs │ ├── OrderItem.cs │ ├── Product.cs │ ├── ShoppingCart.cs │ └── ShoppingCartItem.cs ├── OnlineStore.Services │ ├── CheckoutException.cs │ ├── ICheckoutService.cs │ ├── IProductService.cs │ ├── IServiceCollectionExtensions.cs │ ├── IShoppingCartService.cs │ ├── Implementation │ │ ├── CheckoutService.cs │ │ ├── ProductService.cs │ │ └── ShoppingCartService.cs │ ├── OnlineStore.Services.csproj │ └── ShoppingCartException.cs └── OnlineStore.sln ├── Part1b ├── Checkout.Application │ ├── Checkout.Application.csproj │ ├── Handlers │ │ ├── CreateOrderCommandHandler.cs │ │ └── MakePaymentCommandHandler.cs │ ├── Repositories │ │ ├── IOrderRepository.cs │ │ ├── OrderRepository.cs │ │ └── OrderRepositoryException.cs │ └── ServiceCollectionExtensions.cs ├── Checkout.Commands │ ├── Checkout.Commands.csproj │ ├── CreateOrderCommand.cs │ └── MakePaymentCommand.cs ├── Checkout.Model │ ├── Checkout.Model.csproj │ ├── Order.cs │ └── OrderItem.cs ├── Core.Model │ ├── CommandResponse.cs │ └── Core.Model.csproj ├── OnlineStore.Api │ ├── Controllers │ │ ├── CheckoutController.cs │ │ ├── HomeController.cs │ │ ├── ProductController.cs │ │ └── ShoppingCartController.cs │ ├── Extensions │ │ └── ControllerExtensions.cs │ ├── OnlineStore.Api.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── OnlineStore.sln ├── ShoppingCart.Application │ ├── Handlers │ │ ├── AddToCartCommandHandler.cs │ │ └── GetCartQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repositories │ │ ├── IShoppingCartRepository.cs │ │ └── ShoppingCartRepository.cs │ └── ShoppingCart.Application.csproj ├── ShoppingCart.Commands │ ├── AddToCartCommand.cs │ ├── ClearCartCommand.cs │ ├── GetCartQuery.cs │ └── ShoppingCart.Commands.csproj ├── ShoppingCart.Model │ ├── ShoppingCart.Model.csproj │ ├── ShoppingCart.cs │ └── ShoppingCartItem.cs ├── Store.Application │ ├── Handlers │ │ └── GetStoreProductQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repository │ │ ├── IStoreProductRepository.cs │ │ └── StoreProductRepository.cs │ └── Store.Application.csproj ├── Store.Commands │ ├── GetStoreProductQuery.cs │ └── Store.Commands.csproj └── Store.Model │ ├── Store.Model.csproj │ └── StoreProduct.cs ├── Part2 ├── Checkout.Application │ ├── Checkout.Application.csproj │ ├── Handlers │ │ ├── CreateOrderCommandHandler.cs │ │ └── MakePaymentCommandHandler.cs │ ├── Repositories │ │ ├── IOrderRepository.cs │ │ ├── OrderRepository.cs │ │ └── OrderRepositoryException.cs │ └── ServiceCollectionExtensions.cs ├── Checkout.Commands │ ├── Checkout.Commands.csproj │ ├── CreateOrderCommand.cs │ └── MakePaymentCommand.cs ├── Checkout.Model │ ├── Checkout.Model.csproj │ ├── Order.cs │ └── OrderItem.cs ├── Core.Model │ ├── CommandResponse.cs │ ├── Core.Model.csproj │ └── IUserContextCommand.cs ├── OnlineStore.Api.Tests │ ├── Controllers │ │ └── AbstractCommandControllerShould.cs │ ├── OnlineStore.Api.Tests.csproj │ └── TestAssets │ │ └── SimpleCommand.cs ├── OnlineStore.Api │ ├── Binders │ │ ├── AuthenticatedUserIdAwareBodyModelBinder.cs │ │ └── AuthenticatedUserIdAwareBodyModelBinderProvider.cs │ ├── Controllers │ │ ├── AbstractCommandController.cs │ │ ├── CheckoutController.cs │ │ ├── HomeController.cs │ │ ├── ProductController.cs │ │ └── ShoppingCartController.cs │ ├── Extensions │ │ └── ControllerExtensions.cs │ ├── Filters │ │ └── AssignAuthenticatedUserIdActionFilter.cs │ ├── MetadataProviders │ │ └── AuthenticatedUserIdParameterModelConvention.cs │ ├── OnlineStore.Api.csproj │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── launchSettings.json │ ├── Startup.cs │ ├── Swagger │ │ ├── SwaggerAuthenticatedUserIdFilter.cs │ │ └── SwaggerAuthenticatedUserIdOperationFilter.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── OnlineStore.sln ├── ShoppingCart.Application │ ├── Handlers │ │ ├── AddToCartCommandHandler.cs │ │ └── GetCartQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repositories │ │ ├── IShoppingCartRepository.cs │ │ └── ShoppingCartRepository.cs │ └── ShoppingCart.Application.csproj ├── ShoppingCart.Commands │ ├── AddToCartCommand.cs │ ├── ClearCartCommand.cs │ ├── GetCartQuery.cs │ └── ShoppingCart.Commands.csproj ├── ShoppingCart.Model │ ├── ShoppingCart.Model.csproj │ ├── ShoppingCart.cs │ └── ShoppingCartItem.cs ├── Store.Application │ ├── Handlers │ │ └── GetStoreProductQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repository │ │ ├── IStoreProductRepository.cs │ │ └── StoreProductRepository.cs │ └── Store.Application.csproj ├── Store.Commands │ ├── GetStoreProductQuery.cs │ └── Store.Commands.csproj └── Store.Model │ ├── Store.Model.csproj │ └── StoreProduct.cs ├── Part3 ├── Checkout.Application │ ├── Checkout.Application.csproj │ ├── Handlers │ │ ├── CreateOrderCommandHandler.cs │ │ └── MakePaymentCommandHandler.cs │ ├── Repositories │ │ ├── IOrderRepository.cs │ │ ├── OrderRepository.cs │ │ └── OrderRepositoryException.cs │ └── ServiceCollectionExtensions.cs ├── Checkout.Commands │ ├── Checkout.Commands.csproj │ ├── CreateOrderCommand.cs │ └── MakePaymentCommand.cs ├── Checkout.Model │ ├── Checkout.Model.csproj │ ├── Order.cs │ └── OrderItem.cs ├── Checkout.Validation.Tests │ ├── Checkout.Validation.Tests.csproj │ └── MakePaymentCommandValidatorShould.cs ├── Checkout.Validation │ ├── AssemblyInfo.cs │ ├── Checkout.Validation.csproj │ ├── IServiceCollectionExtensions.cs │ └── MakePaymentCommandValidator.cs ├── Core.Model │ ├── ApplicationException.cs │ ├── CommandError.cs │ ├── CommandResponse.cs │ ├── Core.Model.csproj │ └── IUserContextCommand.cs ├── OnlineStore.Api.Tests │ ├── Controllers │ │ └── AbstractCommandControllerShould.cs │ ├── OnlineStore.Api.Tests.csproj │ └── TestAssets │ │ └── SimpleCommand.cs ├── OnlineStore.Api │ ├── Binders │ │ ├── AuthenticatedUserIdAwareBodyModelBinder.cs │ │ └── AuthenticatedUserIdAwareBodyModelBinderProvider.cs │ ├── Controllers │ │ ├── AbstractCommandController.cs │ │ ├── CheckoutController.cs │ │ ├── HomeController.cs │ │ ├── ProductController.cs │ │ └── ShoppingCartController.cs │ ├── Extensions │ │ ├── CommandResponseExtensions.cs │ │ └── ControllerExtensions.cs │ ├── Filters │ │ └── AssignAuthenticatedUserIdActionFilter.cs │ ├── MetadataProviders │ │ └── AuthenticatedUserIdParameterModelConvention.cs │ ├── OnlineStore.Api.csproj │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── launchSettings.json │ ├── Startup.cs │ ├── Swagger │ │ ├── SwaggerAuthenticatedUserIdFilter.cs │ │ └── SwaggerAuthenticatedUserIdOperationFilter.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── OnlineStore.sln ├── ShoppingCart.Application │ ├── Handlers │ │ ├── AddToCartCommandHandler.cs │ │ └── GetCartQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repositories │ │ ├── IShoppingCartRepository.cs │ │ └── ShoppingCartRepository.cs │ └── ShoppingCart.Application.csproj ├── ShoppingCart.Commands │ ├── AddToCartCommand.cs │ ├── ClearCartCommand.cs │ ├── GetCartQuery.cs │ └── ShoppingCart.Commands.csproj ├── ShoppingCart.Model │ ├── ShoppingCart.Model.csproj │ ├── ShoppingCart.cs │ └── ShoppingCartItem.cs ├── ShoppingCart.Validation.Tests │ ├── AddToCartCommandValidatorShould.cs │ └── ShoppingCart.Validation.Tests.csproj ├── ShoppingCart.Validation │ ├── AddToCartCommandValidator.cs │ ├── AssemblyInfo.cs │ ├── IServiceCollectionExtensions.cs │ └── ShoppingCart.Validation.csproj ├── Store.Application │ ├── Handlers │ │ └── GetStoreProductQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repository │ │ ├── IStoreProductRepository.cs │ │ └── StoreProductRepository.cs │ └── Store.Application.csproj ├── Store.Commands │ ├── GetStoreProductQuery.cs │ └── Store.Commands.csproj ├── Store.Model │ ├── Store.Model.csproj │ └── StoreProduct.cs ├── Store.Validation.Tests │ ├── GetStoreProductQueryValidatorShould.cs │ └── Store.Validation.Tests.csproj └── Store.Validation │ ├── AssemblyInfo.cs │ ├── GetStoreProductQueryValidator.cs │ ├── IServiceCollectionExtensions.cs │ └── Store.Validation.csproj ├── Part4-decorator ├── Checkout.Application │ ├── Checkout.Application.csproj │ ├── Handlers │ │ ├── CreateOrderCommandHandler.cs │ │ └── MakePaymentCommandHandler.cs │ ├── Repositories │ │ ├── IOrderRepository.cs │ │ ├── OrderRepository.cs │ │ └── OrderRepositoryException.cs │ └── ServiceCollectionExtensions.cs ├── Checkout.Commands │ ├── Checkout.Commands.csproj │ ├── CreateOrderCommand.cs │ └── MakePaymentCommand.cs ├── Checkout.Model │ ├── Checkout.Model.csproj │ ├── Order.cs │ └── OrderItem.cs ├── Checkout.Validation.Tests │ ├── Checkout.Validation.Tests.csproj │ └── MakePaymentCommandValidatorShould.cs ├── Checkout.Validation │ ├── AssemblyInfo.cs │ ├── Checkout.Validation.csproj │ ├── IServiceCollectionExtensions.cs │ └── MakePaymentCommandValidator.cs ├── Core.Model │ ├── ApplicationException.cs │ ├── CommandError.cs │ ├── CommandResponse.cs │ ├── Core.Model.csproj │ ├── ILogAwareCommand.cs │ └── IUserContextCommand.cs ├── OnlineStore.Api.Tests │ ├── Commanding │ │ └── LoggingCommandDispatcherShould.cs │ ├── Controllers │ │ └── AbstractCommandControllerShould.cs │ ├── OnlineStore.Api.Tests.csproj │ └── TestAssets │ │ ├── SimpleCommand.cs │ │ └── TestLogger.cs ├── OnlineStore.Api │ ├── Binders │ │ ├── AuthenticatedUserIdAwareBodyModelBinder.cs │ │ └── AuthenticatedUserIdAwareBodyModelBinderProvider.cs │ ├── Commanding │ │ └── LoggingCommandDispatcher.cs │ ├── Controllers │ │ ├── AbstractCommandController.cs │ │ ├── CheckoutController.cs │ │ ├── HomeController.cs │ │ ├── ProductController.cs │ │ └── ShoppingCartController.cs │ ├── Exceptions │ │ └── DispatcherException.cs │ ├── Extensions │ │ ├── CommandResponseExtensions.cs │ │ ├── ControllerExtensions.cs │ │ └── DispatcherExceptionExtensions.cs │ ├── Filters │ │ └── AssignAuthenticatedUserIdActionFilter.cs │ ├── MetadataProviders │ │ └── AuthenticatedUserIdParameterModelConvention.cs │ ├── Metrics │ │ ├── IMetricCollector.cs │ │ ├── IMetricCollectorFactory.cs │ │ ├── MetricCollector.cs │ │ └── MetricCollectorFactory.cs │ ├── OnlineStore.Api.csproj │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── launchSettings.json │ ├── Startup.cs │ ├── Swagger │ │ ├── SwaggerAuthenticatedUserIdFilter.cs │ │ └── SwaggerAuthenticatedUserIdOperationFilter.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── OnlineStore.sln ├── ShoppingCart.Application │ ├── Handlers │ │ ├── AddToCartCommandHandler.cs │ │ └── GetCartQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repositories │ │ ├── IShoppingCartRepository.cs │ │ └── ShoppingCartRepository.cs │ └── ShoppingCart.Application.csproj ├── ShoppingCart.Commands │ ├── AddToCartCommand.cs │ ├── ClearCartCommand.cs │ ├── GetCartQuery.cs │ └── ShoppingCart.Commands.csproj ├── ShoppingCart.Model │ ├── ShoppingCart.Model.csproj │ ├── ShoppingCart.cs │ └── ShoppingCartItem.cs ├── ShoppingCart.Validation.Tests │ ├── AddToCartCommandValidatorShould.cs │ └── ShoppingCart.Validation.Tests.csproj ├── ShoppingCart.Validation │ ├── AddToCartCommandValidator.cs │ ├── AssemblyInfo.cs │ ├── IServiceCollectionExtensions.cs │ └── ShoppingCart.Validation.csproj ├── Store.Application │ ├── Handlers │ │ └── GetStoreProductQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repository │ │ ├── IStoreProductRepository.cs │ │ └── StoreProductRepository.cs │ └── Store.Application.csproj ├── Store.Commands │ ├── GetStoreProductQuery.cs │ └── Store.Commands.csproj ├── Store.Model │ ├── Store.Model.csproj │ └── StoreProduct.cs ├── Store.Validation.Tests │ ├── GetStoreProductQueryValidatorShould.cs │ └── Store.Validation.Tests.csproj └── Store.Validation │ ├── AssemblyInfo.cs │ ├── GetStoreProductQueryValidator.cs │ ├── IServiceCollectionExtensions.cs │ └── Store.Validation.csproj ├── Part4-framework ├── Checkout.Application │ ├── Checkout.Application.csproj │ ├── Handlers │ │ ├── CreateOrderCommandHandler.cs │ │ └── MakePaymentCommandHandler.cs │ ├── Repositories │ │ ├── IOrderRepository.cs │ │ ├── OrderRepository.cs │ │ └── OrderRepositoryException.cs │ └── ServiceCollectionExtensions.cs ├── Checkout.Commands │ ├── Checkout.Commands.csproj │ ├── CreateOrderCommand.cs │ └── MakePaymentCommand.cs ├── Checkout.Model │ ├── Checkout.Model.csproj │ ├── Order.cs │ └── OrderItem.cs ├── Checkout.Validation.Tests │ ├── Checkout.Validation.Tests.csproj │ └── MakePaymentCommandValidatorShould.cs ├── Checkout.Validation │ ├── AssemblyInfo.cs │ ├── Checkout.Validation.csproj │ ├── IServiceCollectionExtensions.cs │ └── MakePaymentCommandValidator.cs ├── Core.Model │ ├── ApplicationException.cs │ ├── CommandError.cs │ ├── CommandResponse.cs │ ├── Core.Model.csproj │ └── IUserContextCommand.cs ├── OnlineStore.Api.Tests │ ├── Controllers │ │ └── AbstractCommandControllerShould.cs │ ├── OnlineStore.Api.Tests.csproj │ └── TestAssets │ │ └── SimpleCommand.cs ├── OnlineStore.Api │ ├── Binders │ │ ├── AuthenticatedUserIdAwareBodyModelBinder.cs │ │ └── AuthenticatedUserIdAwareBodyModelBinderProvider.cs │ ├── Commanding │ │ ├── AuditItemUserIdEnricher.cs │ │ ├── CommandExecutionExceptionHandler.cs │ │ ├── LoggingCommandExecutionAuditor.cs │ │ └── LoggingCommandPreDispatchAuditor.cs │ ├── Controllers │ │ ├── AbstractCommandController.cs │ │ ├── CheckoutController.cs │ │ ├── HomeController.cs │ │ ├── ProductController.cs │ │ └── ShoppingCartController.cs │ ├── Exceptions │ │ └── DispatcherException.cs │ ├── Extensions │ │ ├── CommandResponseExtensions.cs │ │ ├── ControllerExtensions.cs │ │ └── DispatcherExceptionExtensions.cs │ ├── Filters │ │ └── AssignAuthenticatedUserIdActionFilter.cs │ ├── MetadataProviders │ │ └── AuthenticatedUserIdParameterModelConvention.cs │ ├── Metrics │ │ ├── IMetricCollector.cs │ │ └── MetricCollector.cs │ ├── OnlineStore.Api.csproj │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── launchSettings.json │ ├── Startup.cs │ ├── Swagger │ │ ├── SwaggerAuthenticatedUserIdFilter.cs │ │ └── SwaggerAuthenticatedUserIdOperationFilter.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── OnlineStore.sln ├── ShoppingCart.Application │ ├── Handlers │ │ ├── AddToCartCommandHandler.cs │ │ └── GetCartQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repositories │ │ ├── IShoppingCartRepository.cs │ │ └── ShoppingCartRepository.cs │ └── ShoppingCart.Application.csproj ├── ShoppingCart.Commands │ ├── AddToCartCommand.cs │ ├── ClearCartCommand.cs │ ├── GetCartQuery.cs │ └── ShoppingCart.Commands.csproj ├── ShoppingCart.Model │ ├── ShoppingCart.Model.csproj │ ├── ShoppingCart.cs │ └── ShoppingCartItem.cs ├── ShoppingCart.Validation.Tests │ ├── AddToCartCommandValidatorShould.cs │ └── ShoppingCart.Validation.Tests.csproj ├── ShoppingCart.Validation │ ├── AddToCartCommandValidator.cs │ ├── AssemblyInfo.cs │ ├── IServiceCollectionExtensions.cs │ └── ShoppingCart.Validation.csproj ├── Store.Application │ ├── Handlers │ │ └── GetStoreProductQueryHandler.cs │ ├── IServiceCollectionExtensions.cs │ ├── Repository │ │ ├── IStoreProductRepository.cs │ │ └── StoreProductRepository.cs │ └── Store.Application.csproj ├── Store.Commands │ ├── GetStoreProductQuery.cs │ └── Store.Commands.csproj ├── Store.Model │ ├── Store.Model.csproj │ └── StoreProduct.cs ├── Store.Validation.Tests │ ├── GetStoreProductQueryValidatorShould.cs │ └── Store.Validation.Tests.csproj └── Store.Validation │ ├── AssemblyInfo.cs │ ├── GetStoreProductQueryValidator.cs │ ├── IServiceCollectionExtensions.cs │ └── Store.Validation.csproj └── Part5 ├── Checkout.Application ├── Checkout.Application.csproj ├── Handlers │ ├── CreateOrderCommandHandler.cs │ └── MakePaymentCommandHandler.cs ├── Repositories │ ├── IOrderRepository.cs │ ├── OrderRepository.cs │ └── OrderRepositoryException.cs └── ServiceCollectionExtensions.cs ├── Checkout.Commands ├── Checkout.Commands.csproj ├── CreateOrderCommand.cs └── MakePaymentCommand.cs ├── Checkout.Model ├── Checkout.Model.csproj ├── Order.cs └── OrderItem.cs ├── Checkout.Validation.Tests ├── Checkout.Validation.Tests.csproj └── MakePaymentCommandValidatorShould.cs ├── Checkout.Validation ├── AssemblyInfo.cs ├── Checkout.Validation.csproj ├── IServiceCollectionExtensions.cs └── MakePaymentCommandValidator.cs ├── Core.Commanding ├── Commanding │ ├── AuditItemUserIdEnricher.cs │ ├── CommandExecutionExceptionHandler.cs │ ├── LoggingCommandExecutionAuditor.cs │ └── LoggingCommandPreDispatchAuditor.cs ├── Core.Commanding.csproj ├── Exceptions │ └── DispatcherException.cs ├── IServiceCollectionExtensions.cs └── Metrics │ ├── IMetricCollector.cs │ └── MetricCollector.cs ├── Core.Model ├── ApplicationException.cs ├── CommandError.cs ├── CommandResponse.cs ├── Core.Model.csproj └── IUserContextCommand.cs ├── OnlineStore.Api.Tests ├── Controllers │ └── AbstractCommandControllerShould.cs ├── OnlineStore.Api.Tests.csproj └── TestAssets │ └── SimpleCommand.cs ├── OnlineStore.Api ├── Binders │ ├── AuthenticatedUserIdAwareBodyModelBinder.cs │ └── AuthenticatedUserIdAwareBodyModelBinderProvider.cs ├── Controllers │ ├── AbstractCommandController.cs │ ├── CheckoutController.cs │ ├── HomeController.cs │ ├── ProductController.cs │ └── ShoppingCartController.cs ├── Extensions │ ├── CommandResponseExtensions.cs │ ├── ControllerExtensions.cs │ └── DispatcherExceptionExtensions.cs ├── Filters │ └── AssignAuthenticatedUserIdActionFilter.cs ├── MetadataProviders │ └── AuthenticatedUserIdParameterModelConvention.cs ├── OnlineStore.Api.csproj ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ └── launchSettings.json ├── Startup.cs ├── Swagger │ ├── SwaggerAuthenticatedUserIdFilter.cs │ └── SwaggerAuthenticatedUserIdOperationFilter.cs ├── appsettings.Development.json └── appsettings.json ├── OnlineStore.sln ├── ShoppingCart.Application ├── Handlers │ ├── AddToCartCommandHandler.cs │ └── GetCartQueryHandler.cs ├── IServiceCollectionExtensions.cs ├── Repositories │ ├── IShoppingCartRepository.cs │ └── ShoppingCartRepository.cs └── ShoppingCart.Application.csproj ├── ShoppingCart.Commands ├── AddToCartCommand.cs ├── ClearCartCommand.cs ├── GetCartQuery.cs └── ShoppingCart.Commands.csproj ├── ShoppingCart.Model ├── ShoppingCart.Model.csproj ├── ShoppingCart.cs └── ShoppingCartItem.cs ├── ShoppingCart.Validation.Tests ├── AddToCartCommandValidatorShould.cs └── ShoppingCart.Validation.Tests.csproj ├── ShoppingCart.Validation ├── AddToCartCommandValidator.cs ├── AssemblyInfo.cs ├── IServiceCollectionExtensions.cs └── ShoppingCart.Validation.csproj ├── Store.Application ├── Handlers │ └── GetStoreProductQueryHandler.cs ├── IServiceCollectionExtensions.cs ├── Repository │ ├── IStoreProductRepository.cs │ └── StoreProductRepository.cs └── Store.Application.csproj ├── Store.Commands ├── GetStoreProductQuery.cs └── Store.Commands.csproj ├── Store.Functions ├── .gitignore ├── GetStoreProduct.cs ├── Store.Functions.csproj └── host.json ├── Store.Model ├── Store.Model.csproj └── StoreProduct.cs ├── Store.Validation.Tests ├── GetStoreProductQueryValidatorShould.cs └── Store.Validation.Tests.csproj └── Store.Validation ├── AssemblyInfo.cs ├── GetStoreProductQueryValidator.cs ├── IServiceCollectionExtensions.cs └── Store.Validation.csproj /Part1a/OnlineStore.Api/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace OnlineStore.Api.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | [Route(""), HttpGet] 8 | [ApiExplorerSettings(IgnoreApi = true)] 9 | public IActionResult Index() 10 | { 11 | return Redirect("/swagger"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Api/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Mvc; 4 | using OnlineStore.Model; 5 | using OnlineStore.Services; 6 | 7 | namespace OnlineStore.Api.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class ProductController : Controller 11 | { 12 | private readonly IProductService _productService; 13 | 14 | public ProductController(IProductService productService) 15 | { 16 | _productService = productService; 17 | } 18 | 19 | [HttpGet("{id}")] 20 | public async Task Get(Guid id) 21 | { 22 | return await _productService.GetAsync(id); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Api/Extensions/ControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class ControllerExtensions 7 | { 8 | public static Guid GetUserId(this Controller controller) 9 | { 10 | // in reality this would pull the user ID from the claims e.g. 11 | // return Guid.Parse(controller.User.FindFirst("userId").Value); 12 | return Guid.Parse("A9F7EE3A-CB0D-4056-9DB5-AD1CB07D3093"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace OnlineStore.Api 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var host = new WebHostBuilder() 11 | .UseKestrel() 12 | .UseContentRoot(Directory.GetCurrentDirectory()) 13 | .UseIISIntegration() 14 | .UseStartup() 15 | .UseApplicationInsights() 16 | .Build(); 17 | 18 | host.Run(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:50750/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "OnlineStore": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:50751/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Part1a/OnlineStore.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.DataAccess/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using OnlineStore.Model; 4 | 5 | namespace OnlineStore.DataAccess 6 | { 7 | public interface IOrderRepository 8 | { 9 | Task CreateAsync(Order order); 10 | Task UpdateAsync(Order order); 11 | Task GetAsync(Guid orderId); 12 | } 13 | } -------------------------------------------------------------------------------- /Part1a/OnlineStore.DataAccess/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using OnlineStore.DataAccess.Implementation; 3 | 4 | namespace OnlineStore.DataAccess 5 | { 6 | // ReSharper disable once InconsistentNaming 7 | public static class IServiceCollectionExtensions 8 | { 9 | public static IServiceCollection UseDataAccess(this IServiceCollection serviceCollection) 10 | { 11 | serviceCollection.AddSingleton(); 12 | serviceCollection.AddSingleton(); 13 | serviceCollection.AddSingleton(); 14 | 15 | return serviceCollection; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.DataAccess/IShoppingCartRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using OnlineStore.Model; 4 | 5 | namespace OnlineStore.DataAccess 6 | { 7 | public interface IShoppingCartRepository 8 | { 9 | Task UpdateAsync(ShoppingCart cart); 10 | 11 | Task GetAsync(Guid userId); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.DataAccess/IStoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using OnlineStore.Model; 4 | 5 | namespace OnlineStore.DataAccess 6 | { 7 | public interface IStoreProductRepository 8 | { 9 | Task GetAsync(Guid productId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.DataAccess/Implementation/ShoppingCartRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using OnlineStore.Model; 5 | 6 | namespace OnlineStore.DataAccess.Implementation 7 | { 8 | internal class ShoppingCartRepository : IShoppingCartRepository 9 | { 10 | private readonly Dictionary _baskets = new Dictionary(); 11 | 12 | public Task UpdateAsync(ShoppingCart cart) 13 | { 14 | _baskets[cart.UserId] = cart; 15 | return Task.FromResult(0); 16 | } 17 | 18 | public Task GetAsync(Guid userId) 19 | { 20 | _baskets.TryGetValue(userId, out ShoppingCart basket); 21 | return Task.FromResult(basket); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.DataAccess/Implementation/StoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using OnlineStore.Model; 4 | 5 | namespace OnlineStore.DataAccess.Implementation 6 | { 7 | internal class StoreProductRepository : IStoreProductRepository 8 | { 9 | public Task GetAsync(Guid productId) 10 | { 11 | return Task.FromResult(new StoreProduct 12 | { 13 | Id = productId, 14 | Name = "A product", 15 | Price = new Random().Next(200,1000) / 100.0 16 | }); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.DataAccess/OnlineStore.DataAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.DataAccess/RepositoryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OnlineStore.DataAccess 4 | { 5 | class RepositoryException : Exception 6 | { 7 | public RepositoryException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Model/OnlineStore.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Model/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace OnlineStore.Model 6 | { 7 | public class Order 8 | { 9 | public Guid Id { get; set; } 10 | 11 | public Guid UserId { get; set; } 12 | 13 | public DateTime CreatedAtUtc { get; set; } 14 | 15 | public IReadOnlyCollection OrderItems { get; set; } 16 | 17 | public double PercentageDiscountApplied { get; set; } 18 | 19 | public double Total { get; set; } 20 | 21 | public bool PaymentMade { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Model/OrderItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OnlineStore.Model 4 | { 5 | public class OrderItem 6 | { 7 | public Guid ProductId { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public int Quantity { get; set; } 12 | 13 | public double Price { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Model/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OnlineStore.Model 4 | { 5 | public class StoreProduct 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public double Price { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Model/ShoppingCart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OnlineStore.Model 5 | { 6 | public class ShoppingCart 7 | { 8 | public ShoppingCart() 9 | { 10 | Items = new List(); 11 | } 12 | 13 | public Guid UserId { get; set; } 14 | 15 | public IReadOnlyCollection Items { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Model/ShoppingCartItem.cs: -------------------------------------------------------------------------------- 1 | namespace OnlineStore.Model 2 | { 3 | public class ShoppingCartItem 4 | { 5 | public int Quantity { get; set; } 6 | 7 | public StoreProduct Product { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Services/CheckoutException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OnlineStore.Services 4 | { 5 | class CheckoutException : Exception 6 | { 7 | public CheckoutException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Services/ICheckoutService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using OnlineStore.Model; 4 | 5 | namespace OnlineStore.Services 6 | { 7 | public interface ICheckoutService 8 | { 9 | Task CreateOrderAsync(Guid userId); 10 | Task ConfirmPaymentAsync(Guid orderId); 11 | } 12 | } -------------------------------------------------------------------------------- /Part1a/OnlineStore.Services/IProductService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace OnlineStore.Services 5 | { 6 | public interface IProductService 7 | { 8 | Task GetAsync(Guid productId); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Services/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using OnlineStore.DataAccess; 3 | using OnlineStore.Services.Implementation; 4 | 5 | namespace OnlineStore.Services 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection UseServices(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.UseDataAccess(); 13 | serviceCollection.AddTransient(); 14 | serviceCollection.AddTransient(); 15 | serviceCollection.AddTransient(); 16 | return serviceCollection; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Services/IShoppingCartService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace OnlineStore.Services 5 | { 6 | public interface IShoppingCartService 7 | { 8 | Task AddToBasketAsync(Guid userId, Guid productId, int quantity); 9 | 10 | Task GetAsync(Guid userId); 11 | 12 | Task ClearAsync(Guid userId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Services/Implementation/ProductService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using OnlineStore.DataAccess; 4 | 5 | namespace OnlineStore.Services.Implementation 6 | { 7 | class ProductService : IProductService 8 | { 9 | private readonly IStoreProductRepository _storeProductRepository; 10 | 11 | public ProductService(IStoreProductRepository storeProductRepository) 12 | { 13 | _storeProductRepository = storeProductRepository; 14 | } 15 | 16 | public Task GetAsync(Guid productId) 17 | { 18 | return _storeProductRepository.GetAsync(productId); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Services/OnlineStore.Services.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Part1a/OnlineStore.Services/ShoppingCartException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OnlineStore.Services 4 | { 5 | public class ShoppingCartException : Exception 6 | { 7 | public ShoppingCartException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1b/Checkout.Application/Repositories/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Checkout.Model; 4 | 5 | namespace Checkout.Application.Repositories 6 | { 7 | internal interface IOrderRepository 8 | { 9 | Task CreateAsync(Order order); 10 | Task UpdateAsync(Order order); 11 | Task GetAsync(Guid orderId); 12 | } 13 | } -------------------------------------------------------------------------------- /Part1b/Checkout.Application/Repositories/OrderRepositoryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Application.Repositories 4 | { 5 | internal class OrderRepositoryException : Exception 6 | { 7 | public OrderRepositoryException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1b/Checkout.Application/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Checkout.Application.Handlers; 3 | using Checkout.Application.Repositories; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Checkout.Application 7 | { 8 | public static class ServiceCollectionExtensions 9 | { 10 | public static IServiceCollection UseCheckout(this IServiceCollection serviceCollection, 11 | ICommandRegistry registry) 12 | { 13 | serviceCollection.AddSingleton(); 14 | 15 | registry.Register(); 16 | registry.Register(); 17 | 18 | return serviceCollection; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part1b/Checkout.Commands/Checkout.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part1b/Checkout.Commands/CreateOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Checkout.Model; 4 | using Core.Model; 5 | 6 | namespace Checkout.Commands 7 | { 8 | public class CreateOrderCommand : ICommand> 9 | { 10 | public Guid UserId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part1b/Checkout.Commands/MakePaymentCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace Checkout.Commands 6 | { 7 | public class MakePaymentCommand : ICommand 8 | { 9 | public Guid OrderId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1b/Checkout.Model/Checkout.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part1b/Checkout.Model/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Checkout.Model 5 | { 6 | public class Order 7 | { 8 | public Guid Id { get; set; } 9 | 10 | public Guid UserId { get; set; } 11 | 12 | public DateTime CreatedAtUtc { get; set; } 13 | 14 | public IReadOnlyCollection OrderItems { get; set; } 15 | 16 | public double PercentageDiscountApplied { get; set; } 17 | 18 | public double Total { get; set; } 19 | 20 | public bool PaymentMade { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part1b/Checkout.Model/OrderItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Model 4 | { 5 | public class OrderItem 6 | { 7 | public Guid ProductId { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public int Quantity { get; set; } 12 | 13 | public double Price { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part1b/Core.Model/Core.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part1b/OnlineStore.Api/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace OnlineStore.Api.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | [Route(""), HttpGet] 8 | [ApiExplorerSettings(IgnoreApi = true)] 9 | public IActionResult Index() 10 | { 11 | return Redirect("/swagger"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part1b/OnlineStore.Api/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AzureFromTheTrenches.Commanding.Abstractions; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Store.Commands; 6 | using Store.Model; 7 | 8 | namespace OnlineStore.Api.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | public class ProductController : Controller 12 | { 13 | private readonly ICommandDispatcher _dispatcher; 14 | 15 | public ProductController(ICommandDispatcher dispatcher) 16 | { 17 | _dispatcher = dispatcher; 18 | } 19 | 20 | [HttpGet("{id}")] 21 | public async Task Get(Guid id) 22 | { 23 | return await _dispatcher.DispatchAsync(new GetStoreProductQuery 24 | { 25 | ProductId = id 26 | }); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Part1b/OnlineStore.Api/Extensions/ControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class ControllerExtensions 7 | { 8 | public static Guid GetUserId(this Controller controller) 9 | { 10 | // in reality this would pull the user ID from the claims e.g. 11 | // return Guid.Parse(controller.User.FindFirst("userId").Value); 12 | return Guid.Parse("A9F7EE3A-CB0D-4056-9DB5-AD1CB07D3093"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part1b/OnlineStore.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace OnlineStore.Api 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part1b/OnlineStore.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55161/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "OnlineStore": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:55162/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Part1b/OnlineStore.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part1b/OnlineStore.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Application/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ShoppingCart.Application.Handlers; 4 | using ShoppingCart.Application.Repositories; 5 | 6 | namespace ShoppingCart.Application 7 | { 8 | // ReSharper disable once InconsistentNaming 9 | public static class IServiceCollectionExtensions 10 | { 11 | public static IServiceCollection UseShoppingCart(this IServiceCollection serviceCollection, 12 | ICommandRegistry commandRegistry) 13 | { 14 | serviceCollection.AddSingleton(); 15 | 16 | commandRegistry.Register(); 17 | commandRegistry.Register(); 18 | 19 | return serviceCollection; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Application/Repositories/IShoppingCartRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ShoppingCart.Application.Repositories 5 | { 6 | internal interface IShoppingCartRepository 7 | { 8 | Task UpdateAsync(Model.ShoppingCart cart); 9 | 10 | Task GetActualOrDefaultAsync(Guid userId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Application/ShoppingCart.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Commands/AddToCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class AddToCartCommand : ICommand 8 | { 9 | public Guid UserId { get; set; } 10 | 11 | public Guid ProductId { get; set; } 12 | 13 | public int Quantity { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Commands/ClearCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class ClearCartCommand : ICommand 8 | { 9 | public Guid UserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Commands/GetCartQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | 4 | namespace ShoppingCart.Commands 5 | { 6 | public class GetCartQuery : ICommand 7 | { 8 | public Guid UserId { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Commands/ShoppingCart.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Model/ShoppingCart.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Model/ShoppingCart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ShoppingCart.Model 5 | { 6 | public class ShoppingCart 7 | { 8 | public ShoppingCart() 9 | { 10 | Items = new List(); 11 | } 12 | 13 | public Guid UserId { get; set; } 14 | 15 | public IReadOnlyCollection Items { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Part1b/ShoppingCart.Model/ShoppingCartItem.cs: -------------------------------------------------------------------------------- 1 | using Store.Model; 2 | 3 | namespace ShoppingCart.Model 4 | { 5 | public class ShoppingCartItem 6 | { 7 | public int Quantity { get; set; } 8 | 9 | public StoreProduct Product { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1b/Store.Application/Handlers/GetStoreProductQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Store.Application.Repository; 4 | using Store.Commands; 5 | using Store.Model; 6 | 7 | namespace Store.Application.Handlers 8 | { 9 | internal class GetStoreProductQueryHandler : ICommandHandler 10 | { 11 | private readonly IStoreProductRepository _repository; 12 | 13 | public GetStoreProductQueryHandler(IStoreProductRepository repository) 14 | { 15 | _repository = repository; 16 | } 17 | 18 | public Task ExecuteAsync(GetStoreProductQuery command, StoreProduct previousResult) 19 | { 20 | return _repository.GetAsync(command.ProductId); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Part1b/Store.Application/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Store.Application.Handlers; 5 | using Store.Application.Repository; 6 | 7 | namespace Store.Application 8 | { 9 | // ReSharper disable once InconsistentNaming 10 | public static class IServiceCollectionExtensions 11 | { 12 | public static IServiceCollection UseStore(this IServiceCollection serviceCollection, 13 | ICommandRegistry commandRegistry) 14 | { 15 | serviceCollection.AddSingleton(); 16 | 17 | commandRegistry.Register(); 18 | 19 | return serviceCollection; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part1b/Store.Application/Repository/IStoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal interface IStoreProductRepository 8 | { 9 | Task GetAsync(Guid productId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1b/Store.Application/Repository/StoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal class StoreProductRepository : IStoreProductRepository 8 | { 9 | public Task GetAsync(Guid productId) 10 | { 11 | return Task.FromResult(new StoreProduct 12 | { 13 | Id = productId, 14 | Name = "A product", 15 | Price = new Random().Next(200,1000) / 100.0 16 | }); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Part1b/Store.Application/Store.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Part1b/Store.Commands/GetStoreProductQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Store.Model; 4 | 5 | namespace Store.Commands 6 | { 7 | public class GetStoreProductQuery : ICommand 8 | { 9 | public Guid ProductId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part1b/Store.Commands/Store.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Part1b/Store.Model/Store.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part1b/Store.Model/StoreProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Store.Model 4 | { 5 | public class StoreProduct 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public double Price { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part2/Checkout.Application/Repositories/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Checkout.Model; 4 | 5 | namespace Checkout.Application.Repositories 6 | { 7 | internal interface IOrderRepository 8 | { 9 | Task CreateAsync(Order order); 10 | Task UpdateAsync(Order order); 11 | Task GetAsync(Guid orderId); 12 | } 13 | } -------------------------------------------------------------------------------- /Part2/Checkout.Application/Repositories/OrderRepositoryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Application.Repositories 4 | { 5 | internal class OrderRepositoryException : Exception 6 | { 7 | public OrderRepositoryException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part2/Checkout.Application/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Checkout.Application.Handlers; 3 | using Checkout.Application.Repositories; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Checkout.Application 7 | { 8 | public static class ServiceCollectionExtensions 9 | { 10 | public static IServiceCollection UseCheckout(this IServiceCollection serviceCollection, 11 | ICommandRegistry registry) 12 | { 13 | serviceCollection.AddSingleton(); 14 | 15 | registry.Register(); 16 | registry.Register(); 17 | 18 | return serviceCollection; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part2/Checkout.Commands/Checkout.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part2/Checkout.Commands/CreateOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Checkout.Model; 4 | using Core.Model; 5 | 6 | namespace Checkout.Commands 7 | { 8 | public class CreateOrderCommand : ICommand>, IUserContextCommand 9 | { 10 | public Guid AuthenticatedUserId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part2/Checkout.Commands/MakePaymentCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace Checkout.Commands 6 | { 7 | public class MakePaymentCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid OrderId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part2/Checkout.Model/Checkout.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part2/Checkout.Model/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Checkout.Model 5 | { 6 | public class Order 7 | { 8 | public Guid Id { get; set; } 9 | 10 | public Guid UserId { get; set; } 11 | 12 | public DateTime CreatedAtUtc { get; set; } 13 | 14 | public IReadOnlyCollection OrderItems { get; set; } 15 | 16 | public double PercentageDiscountApplied { get; set; } 17 | 18 | public double Total { get; set; } 19 | 20 | public bool PaymentMade { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part2/Checkout.Model/OrderItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Model 4 | { 5 | public class OrderItem 6 | { 7 | public Guid ProductId { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public int Quantity { get; set; } 12 | 13 | public double Price { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part2/Core.Model/Core.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part2/Core.Model/IUserContextCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public interface IUserContextCommand 6 | { 7 | Guid AuthenticatedUserId { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api.Tests/OnlineStore.Api.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api.Tests/TestAssets/SimpleCommand.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Core.Model; 3 | 4 | namespace OnlineStore.Api.Tests.TestAssets 5 | { 6 | internal class SimpleCommandNoResult : ICommand 7 | { 8 | } 9 | 10 | internal class SimpleCommandWithResult : ICommand 11 | { 12 | 13 | } 14 | 15 | internal class SimpleCommandCommandResponse : ICommand 16 | { 17 | 18 | } 19 | 20 | internal class SimpleCommandCommandResponseResult : ICommand> 21 | { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Binders/AuthenticatedUserIdAwareBodyModelBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Core.Model; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding; 5 | 6 | namespace OnlineStore.Api.Binders 7 | { 8 | public class AuthenticatedUserIdAwareBodyModelBinder : IModelBinder 9 | { 10 | private readonly IModelBinder _decoratedBinder; 11 | 12 | public AuthenticatedUserIdAwareBodyModelBinder(IModelBinder decoratedBinder) 13 | { 14 | _decoratedBinder = decoratedBinder; 15 | } 16 | 17 | public async Task BindModelAsync(ModelBindingContext bindingContext) 18 | { 19 | await _decoratedBinder.BindModelAsync(bindingContext); 20 | if (bindingContext.Result.Model is IUserContextCommand command) 21 | { 22 | command.AuthenticatedUserId = Guid.Empty; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace OnlineStore.Api.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | [Route(""), HttpGet] 8 | [ApiExplorerSettings(IgnoreApi = true)] 9 | public IActionResult Index() 10 | { 11 | return Redirect("/swagger"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Store.Commands; 5 | using Store.Model; 6 | 7 | namespace OnlineStore.Api.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class ProductController : AbstractCommandController 11 | { 12 | public ProductController(ICommandDispatcher dispatcher) : base(dispatcher) 13 | { 14 | 15 | } 16 | 17 | [HttpGet("{productId}")] 18 | [ProducesResponseType(typeof(StoreProduct), 200)] 19 | public async Task Get([FromRoute] GetStoreProductQuery query) => await ExecuteCommand(query); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Extensions/ControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class ControllerExtensions 7 | { 8 | public static Guid GetUserId(this Controller controller) 9 | { 10 | // in reality this would pull the user ID from the claims e.g. 11 | // return Guid.Parse(controller.User.FindFirst("userId").Value); 12 | return Guid.Parse("A9F7EE3A-CB0D-4056-9DB5-AD1CB07D3093"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Filters/AssignAuthenticatedUserIdActionFilter.cs: -------------------------------------------------------------------------------- 1 | using Core.Model; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | using OnlineStore.Api.Extensions; 5 | 6 | namespace OnlineStore.Api.Filters 7 | { 8 | public class AssignAuthenticatedUserIdActionFilter : IActionFilter 9 | { 10 | public void OnActionExecuting(ActionExecutingContext context) 11 | { 12 | foreach (object parameter in context.ActionArguments.Values) 13 | { 14 | if (parameter is IUserContextCommand userContextCommand) 15 | { 16 | userContextCommand.AuthenticatedUserId = ((Controller) context.Controller).GetUserId(); 17 | } 18 | } 19 | } 20 | 21 | public void OnActionExecuted(ActionExecutedContext context) 22 | { 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace OnlineStore.Api 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("OnlineStore.Api.Tests")] -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55161/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "OnlineStore": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:55162/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Swagger/SwaggerAuthenticatedUserIdFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Model; 3 | using Swashbuckle.AspNetCore.Swagger; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | 6 | namespace OnlineStore.Api.Swagger 7 | { 8 | public class SwaggerAuthenticatedUserIdFilter : ISchemaFilter 9 | { 10 | private const string AuthenticatedUserIdProperty = "authenticatedUserId"; 11 | private static readonly Type UserContextCommandType = typeof(IUserContextCommand); 12 | 13 | public void Apply(Schema model, SchemaFilterContext context) 14 | { 15 | if (UserContextCommandType.IsAssignableFrom(context.SystemType)) 16 | { 17 | if (model.Properties.ContainsKey(AuthenticatedUserIdProperty)) 18 | { 19 | model.Properties.Remove(AuthenticatedUserIdProperty); 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/Swagger/SwaggerAuthenticatedUserIdOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Swashbuckle.AspNetCore.Swagger; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | 5 | namespace OnlineStore.Api.Swagger 6 | { 7 | public class SwaggerAuthenticatedUserIdOperationFilter : IOperationFilter 8 | { 9 | private const string AuthenticatedUserIdProperty = "authenticateduserid"; 10 | 11 | public void Apply(Operation operation, OperationFilterContext context) 12 | { 13 | IParameter authenticatedUserIdParameter = operation.Parameters?.SingleOrDefault(x => x.Name.ToLower() == AuthenticatedUserIdProperty); 14 | if (authenticatedUserIdParameter != null) 15 | { 16 | operation.Parameters.Remove(authenticatedUserIdParameter); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part2/OnlineStore.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Application/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ShoppingCart.Application.Handlers; 4 | using ShoppingCart.Application.Repositories; 5 | 6 | namespace ShoppingCart.Application 7 | { 8 | // ReSharper disable once InconsistentNaming 9 | public static class IServiceCollectionExtensions 10 | { 11 | public static IServiceCollection UseShoppingCart(this IServiceCollection serviceCollection, 12 | ICommandRegistry commandRegistry) 13 | { 14 | serviceCollection.AddSingleton(); 15 | 16 | commandRegistry.Register(); 17 | commandRegistry.Register(); 18 | 19 | return serviceCollection; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Application/Repositories/IShoppingCartRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ShoppingCart.Application.Repositories 5 | { 6 | internal interface IShoppingCartRepository 7 | { 8 | Task UpdateAsync(Model.ShoppingCart cart); 9 | 10 | Task GetActualOrDefaultAsync(Guid userId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Application/ShoppingCart.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Commands/AddToCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class AddToCartCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid ProductId { get; set; } 12 | 13 | public int Quantity { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Commands/ClearCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class ClearCartCommand : ICommand 8 | { 9 | public Guid UserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Commands/GetCartQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class GetCartQuery : ICommand>, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Commands/ShoppingCart.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Model/ShoppingCart.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Model/ShoppingCart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ShoppingCart.Model 5 | { 6 | public class ShoppingCart 7 | { 8 | public ShoppingCart() 9 | { 10 | Items = new List(); 11 | } 12 | 13 | public Guid UserId { get; set; } 14 | 15 | public IReadOnlyCollection Items { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Part2/ShoppingCart.Model/ShoppingCartItem.cs: -------------------------------------------------------------------------------- 1 | using Store.Model; 2 | 3 | namespace ShoppingCart.Model 4 | { 5 | public class ShoppingCartItem 6 | { 7 | public int Quantity { get; set; } 8 | 9 | public StoreProduct Product { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part2/Store.Application/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Store.Application.Handlers; 5 | using Store.Application.Repository; 6 | 7 | namespace Store.Application 8 | { 9 | // ReSharper disable once InconsistentNaming 10 | public static class IServiceCollectionExtensions 11 | { 12 | public static IServiceCollection UseStore(this IServiceCollection serviceCollection, 13 | ICommandRegistry commandRegistry) 14 | { 15 | serviceCollection.AddSingleton(); 16 | 17 | commandRegistry.Register(); 18 | 19 | return serviceCollection; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part2/Store.Application/Repository/IStoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal interface IStoreProductRepository 8 | { 9 | Task GetAsync(Guid productId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part2/Store.Application/Repository/StoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal class StoreProductRepository : IStoreProductRepository 8 | { 9 | public Task GetAsync(Guid productId) 10 | { 11 | return Task.FromResult(new StoreProduct 12 | { 13 | Id = productId, 14 | Name = "A product", 15 | Price = new Random().Next(200,1000) / 100.0 16 | }); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Part2/Store.Application/Store.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Part2/Store.Commands/GetStoreProductQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | using Store.Model; 5 | 6 | namespace Store.Commands 7 | { 8 | public class GetStoreProductQuery : ICommand> 9 | { 10 | public Guid ProductId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part2/Store.Commands/Store.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part2/Store.Model/Store.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part2/Store.Model/StoreProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Store.Model 4 | { 5 | public class StoreProduct 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public double Price { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part3/Checkout.Application/Repositories/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Checkout.Model; 4 | 5 | namespace Checkout.Application.Repositories 6 | { 7 | internal interface IOrderRepository 8 | { 9 | Task CreateAsync(Order order); 10 | Task UpdateAsync(Order order); 11 | Task GetAsync(Guid orderId); 12 | } 13 | } -------------------------------------------------------------------------------- /Part3/Checkout.Application/Repositories/OrderRepositoryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Application.Repositories 4 | { 5 | internal class OrderRepositoryException : Exception 6 | { 7 | public OrderRepositoryException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part3/Checkout.Application/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Checkout.Application.Handlers; 3 | using Checkout.Application.Repositories; 4 | using Checkout.Validation; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Checkout.Application 8 | { 9 | public static class ServiceCollectionExtensions 10 | { 11 | public static IServiceCollection UseCheckout(this IServiceCollection serviceCollection, 12 | ICommandRegistry registry) 13 | { 14 | serviceCollection.AddSingleton(); 15 | 16 | registry.Register(); 17 | registry.Register(); 18 | 19 | serviceCollection.RegisterValidators(); 20 | 21 | return serviceCollection; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part3/Checkout.Commands/Checkout.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part3/Checkout.Commands/CreateOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Checkout.Model; 4 | using Core.Model; 5 | 6 | namespace Checkout.Commands 7 | { 8 | public class CreateOrderCommand : ICommand>, IUserContextCommand 9 | { 10 | public Guid AuthenticatedUserId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part3/Checkout.Commands/MakePaymentCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace Checkout.Commands 6 | { 7 | public class MakePaymentCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid OrderId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part3/Checkout.Model/Checkout.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part3/Checkout.Model/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Checkout.Model 5 | { 6 | public class Order 7 | { 8 | public Guid Id { get; set; } 9 | 10 | public Guid UserId { get; set; } 11 | 12 | public DateTime CreatedAtUtc { get; set; } 13 | 14 | public IReadOnlyCollection OrderItems { get; set; } 15 | 16 | public double PercentageDiscountApplied { get; set; } 17 | 18 | public double Total { get; set; } 19 | 20 | public bool PaymentMade { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part3/Checkout.Model/OrderItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Model 4 | { 5 | public class OrderItem 6 | { 7 | public Guid ProductId { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public int Quantity { get; set; } 12 | 13 | public double Price { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part3/Checkout.Validation.Tests/Checkout.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Part3/Checkout.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Checkout.Validation.Tests")] 4 | -------------------------------------------------------------------------------- /Part3/Checkout.Validation/Checkout.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part3/Checkout.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Checkout.Commands; 2 | using FluentValidation; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Checkout.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, MakePaymentCommandValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part3/Checkout.Validation/MakePaymentCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Checkout.Commands; 3 | using FluentValidation; 4 | 5 | namespace Checkout.Validation 6 | { 7 | class MakePaymentCommandValidator : AbstractValidator 8 | { 9 | public MakePaymentCommandValidator() 10 | { 11 | RuleFor(c => c.OrderId).NotEqual(Guid.Empty); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part3/Core.Model/ApplicationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public class ApplicationException : Exception 6 | { 7 | public ApplicationException(string message) : base(message) 8 | { 9 | } 10 | 11 | public ApplicationException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part3/Core.Model/CommandError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Model 6 | { 7 | public class CommandError 8 | { 9 | public CommandError(string message) 10 | { 11 | Key = ""; 12 | Message = message; 13 | } 14 | 15 | public CommandError(string key, string message) 16 | { 17 | Key = key; 18 | Message = message; 19 | } 20 | 21 | public string Key { get; } 22 | 23 | public string Message { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Part3/Core.Model/Core.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part3/Core.Model/IUserContextCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public interface IUserContextCommand 6 | { 7 | Guid AuthenticatedUserId { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api.Tests/OnlineStore.Api.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api.Tests/TestAssets/SimpleCommand.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Core.Model; 3 | 4 | namespace OnlineStore.Api.Tests.TestAssets 5 | { 6 | internal class SimpleCommandNoResult : ICommand 7 | { 8 | } 9 | 10 | internal class SimpleCommandWithResult : ICommand 11 | { 12 | 13 | } 14 | 15 | internal class SimpleCommandCommandResponse : ICommand 16 | { 17 | 18 | } 19 | 20 | internal class SimpleCommandCommandResponseResult : ICommand> 21 | { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Binders/AuthenticatedUserIdAwareBodyModelBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Core.Model; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding; 5 | 6 | namespace OnlineStore.Api.Binders 7 | { 8 | public class AuthenticatedUserIdAwareBodyModelBinder : IModelBinder 9 | { 10 | private readonly IModelBinder _decoratedBinder; 11 | 12 | public AuthenticatedUserIdAwareBodyModelBinder(IModelBinder decoratedBinder) 13 | { 14 | _decoratedBinder = decoratedBinder; 15 | } 16 | 17 | public async Task BindModelAsync(ModelBindingContext bindingContext) 18 | { 19 | await _decoratedBinder.BindModelAsync(bindingContext); 20 | if (bindingContext.Result.Model is IUserContextCommand command) 21 | { 22 | command.AuthenticatedUserId = Guid.Empty; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace OnlineStore.Api.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | [Route(""), HttpGet] 8 | [ApiExplorerSettings(IgnoreApi = true)] 9 | public IActionResult Index() 10 | { 11 | return Redirect("/swagger"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Store.Commands; 5 | using Store.Model; 6 | 7 | namespace OnlineStore.Api.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class ProductController : AbstractCommandController 11 | { 12 | public ProductController(ICommandDispatcher dispatcher) : base(dispatcher) 13 | { 14 | 15 | } 16 | 17 | [HttpGet("{productId}")] 18 | [ProducesResponseType(typeof(StoreProduct), 200)] 19 | public async Task Get([FromRoute] GetStoreProductQuery query) => await ExecuteCommand(query); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Extensions/CommandResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Core.Model; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class CommandResponseExtensions 7 | { 8 | public static void AddToModelState( 9 | this CommandResponse commandResponse, 10 | ModelStateDictionary modelStateDictionary) 11 | { 12 | foreach (CommandError error in commandResponse.Errors) 13 | { 14 | modelStateDictionary.AddModelError(error.Key ?? "", error.Message); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Extensions/ControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class ControllerExtensions 7 | { 8 | public static Guid GetUserId(this Controller controller) 9 | { 10 | // in reality this would pull the user ID from the claims e.g. 11 | // return Guid.Parse(controller.User.FindFirst("userId").Value); 12 | return Guid.Parse("A9F7EE3A-CB0D-4056-9DB5-AD1CB07D3093"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Filters/AssignAuthenticatedUserIdActionFilter.cs: -------------------------------------------------------------------------------- 1 | using Core.Model; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | using OnlineStore.Api.Extensions; 5 | 6 | namespace OnlineStore.Api.Filters 7 | { 8 | public class AssignAuthenticatedUserIdActionFilter : IActionFilter 9 | { 10 | public void OnActionExecuting(ActionExecutingContext context) 11 | { 12 | foreach (object parameter in context.ActionArguments.Values) 13 | { 14 | if (parameter is IUserContextCommand userContextCommand) 15 | { 16 | userContextCommand.AuthenticatedUserId = ((Controller) context.Controller).GetUserId(); 17 | } 18 | } 19 | } 20 | 21 | public void OnActionExecuted(ActionExecutedContext context) 22 | { 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace OnlineStore.Api 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("OnlineStore.Api.Tests")] -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55161/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "OnlineStore": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:55162/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Swagger/SwaggerAuthenticatedUserIdFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Model; 3 | using Swashbuckle.AspNetCore.Swagger; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | 6 | namespace OnlineStore.Api.Swagger 7 | { 8 | public class SwaggerAuthenticatedUserIdFilter : ISchemaFilter 9 | { 10 | private const string AuthenticatedUserIdProperty = "authenticatedUserId"; 11 | private static readonly Type UserContextCommandType = typeof(IUserContextCommand); 12 | 13 | public void Apply(Schema model, SchemaFilterContext context) 14 | { 15 | if (UserContextCommandType.IsAssignableFrom(context.SystemType)) 16 | { 17 | if (model.Properties.ContainsKey(AuthenticatedUserIdProperty)) 18 | { 19 | model.Properties.Remove(AuthenticatedUserIdProperty); 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/Swagger/SwaggerAuthenticatedUserIdOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Swashbuckle.AspNetCore.Swagger; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | 5 | namespace OnlineStore.Api.Swagger 6 | { 7 | public class SwaggerAuthenticatedUserIdOperationFilter : IOperationFilter 8 | { 9 | private const string AuthenticatedUserIdProperty = "authenticateduserid"; 10 | 11 | public void Apply(Operation operation, OperationFilterContext context) 12 | { 13 | IParameter authenticatedUserIdParameter = operation.Parameters?.SingleOrDefault(x => x.Name.ToLower() == AuthenticatedUserIdProperty); 14 | if (authenticatedUserIdParameter != null) 15 | { 16 | operation.Parameters.Remove(authenticatedUserIdParameter); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part3/OnlineStore.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Application/Repositories/IShoppingCartRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ShoppingCart.Application.Repositories 5 | { 6 | internal interface IShoppingCartRepository 7 | { 8 | Task UpdateAsync(Model.ShoppingCart cart); 9 | 10 | Task GetActualOrDefaultAsync(Guid userId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Commands/AddToCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class AddToCartCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid ProductId { get; set; } 12 | 13 | public int Quantity { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Commands/ClearCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class ClearCartCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Commands/GetCartQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class GetCartQuery : ICommand>, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Commands/ShoppingCart.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Model/ShoppingCart.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Model/ShoppingCart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ShoppingCart.Model 5 | { 6 | public class ShoppingCart 7 | { 8 | public ShoppingCart() 9 | { 10 | Items = new List(); 11 | } 12 | 13 | public Guid UserId { get; set; } 14 | 15 | public IReadOnlyCollection Items { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Model/ShoppingCartItem.cs: -------------------------------------------------------------------------------- 1 | using Store.Model; 2 | 3 | namespace ShoppingCart.Model 4 | { 5 | public class ShoppingCartItem 6 | { 7 | public int Quantity { get; set; } 8 | 9 | public StoreProduct Product { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Validation.Tests/ShoppingCart.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Validation/AddToCartCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentValidation; 3 | using ShoppingCart.Commands; 4 | 5 | namespace ShoppingCart.Validation 6 | { 7 | internal class AddToCartCommandValidator : AbstractValidator 8 | { 9 | public AddToCartCommandValidator() 10 | { 11 | RuleFor(c => c.ProductId).NotEqual(Guid.Empty); 12 | RuleFor(c => c.Quantity).GreaterThan(0); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ShoppingCart.Validation.Tests")] -------------------------------------------------------------------------------- /Part3/ShoppingCart.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ShoppingCart.Commands; 4 | 5 | namespace ShoppingCart.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, AddToCartCommandValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part3/ShoppingCart.Validation/ShoppingCart.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part3/Store.Application/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Store.Application.Handlers; 4 | using Store.Application.Repository; 5 | using Store.Validation; 6 | 7 | namespace Store.Application 8 | { 9 | // ReSharper disable once InconsistentNaming 10 | public static class IServiceCollectionExtensions 11 | { 12 | public static IServiceCollection UseStore(this IServiceCollection serviceCollection, 13 | ICommandRegistry commandRegistry) 14 | { 15 | serviceCollection.AddSingleton(); 16 | 17 | commandRegistry.Register(); 18 | 19 | serviceCollection.RegisterValidators(); 20 | 21 | return serviceCollection; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part3/Store.Application/Repository/IStoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal interface IStoreProductRepository 8 | { 9 | Task GetAsync(Guid productId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part3/Store.Application/Repository/StoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal class StoreProductRepository : IStoreProductRepository 8 | { 9 | public Task GetAsync(Guid productId) 10 | { 11 | return Task.FromResult(new StoreProduct 12 | { 13 | Id = productId, 14 | Name = "A product", 15 | Price = new Random().Next(200,1000) / 100.0 16 | }); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Part3/Store.Application/Store.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Part3/Store.Commands/GetStoreProductQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | using Store.Model; 5 | 6 | namespace Store.Commands 7 | { 8 | public class GetStoreProductQuery : ICommand> 9 | { 10 | public Guid ProductId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part3/Store.Commands/Store.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part3/Store.Model/Store.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part3/Store.Model/StoreProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Store.Model 4 | { 5 | public class StoreProduct 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public double Price { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part3/Store.Validation.Tests/Store.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Part3/Store.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Store.Validation.Tests")] -------------------------------------------------------------------------------- /Part3/Store.Validation/GetStoreProductQueryValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentValidation; 3 | using Store.Commands; 4 | 5 | namespace Store.Validation 6 | { 7 | internal class GetStoreProductQueryValidator : AbstractValidator 8 | { 9 | public GetStoreProductQueryValidator() 10 | { 11 | RuleFor(c => c.ProductId).NotEqual(Guid.Empty); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part3/Store.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Store.Commands; 4 | 5 | namespace Store.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, GetStoreProductQueryValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part3/Store.Validation/Store.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Application/Repositories/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Checkout.Model; 4 | 5 | namespace Checkout.Application.Repositories 6 | { 7 | internal interface IOrderRepository 8 | { 9 | Task CreateAsync(Order order); 10 | Task UpdateAsync(Order order); 11 | Task GetAsync(Guid orderId); 12 | } 13 | } -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Application/Repositories/OrderRepositoryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Application.Repositories 4 | { 5 | internal class OrderRepositoryException : Exception 6 | { 7 | public OrderRepositoryException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Application/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Checkout.Application.Handlers; 3 | using Checkout.Application.Repositories; 4 | using Checkout.Validation; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Checkout.Application 8 | { 9 | public static class ServiceCollectionExtensions 10 | { 11 | public static IServiceCollection UseCheckout(this IServiceCollection serviceCollection, 12 | ICommandRegistry registry) 13 | { 14 | serviceCollection.AddSingleton(); 15 | 16 | registry.Register(); 17 | registry.Register(); 18 | 19 | serviceCollection.RegisterValidators(); 20 | 21 | return serviceCollection; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Commands/Checkout.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Commands/MakePaymentCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace Checkout.Commands 6 | { 7 | public class MakePaymentCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid OrderId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Model/Checkout.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Model/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Checkout.Model 5 | { 6 | public class Order 7 | { 8 | public Guid Id { get; set; } 9 | 10 | public Guid UserId { get; set; } 11 | 12 | public DateTime CreatedAtUtc { get; set; } 13 | 14 | public IReadOnlyCollection OrderItems { get; set; } 15 | 16 | public double PercentageDiscountApplied { get; set; } 17 | 18 | public double Total { get; set; } 19 | 20 | public bool PaymentMade { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Model/OrderItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Model 4 | { 5 | public class OrderItem 6 | { 7 | public Guid ProductId { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public int Quantity { get; set; } 12 | 13 | public double Price { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Validation.Tests/Checkout.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Checkout.Validation.Tests")] 4 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Validation/Checkout.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Checkout.Commands; 2 | using FluentValidation; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Checkout.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, MakePaymentCommandValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part4-decorator/Checkout.Validation/MakePaymentCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Checkout.Commands; 3 | using FluentValidation; 4 | 5 | namespace Checkout.Validation 6 | { 7 | class MakePaymentCommandValidator : AbstractValidator 8 | { 9 | public MakePaymentCommandValidator() 10 | { 11 | RuleFor(c => c.OrderId).NotEqual(Guid.Empty); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part4-decorator/Core.Model/ApplicationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public class ApplicationException : Exception 6 | { 7 | public ApplicationException(string message) : base(message) 8 | { 9 | } 10 | 11 | public ApplicationException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-decorator/Core.Model/CommandError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Model 6 | { 7 | public class CommandError 8 | { 9 | public CommandError(string message) 10 | { 11 | Key = ""; 12 | Message = message; 13 | } 14 | 15 | public CommandError(string key, string message) 16 | { 17 | Key = key; 18 | Message = message; 19 | } 20 | 21 | public string Key { get; } 22 | 23 | public string Message { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Part4-decorator/Core.Model/Core.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part4-decorator/Core.Model/ILogAwareCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public interface ILogAwareCommand 6 | { 7 | string GetPreDispatchLogMessage(); 8 | string GetPostDispatchLogMessage(); 9 | string GetDispatchErrorLogMessage(Exception ex); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-decorator/Core.Model/IUserContextCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public interface IUserContextCommand 6 | { 7 | Guid AuthenticatedUserId { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api.Tests/OnlineStore.Api.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api.Tests/TestAssets/SimpleCommand.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Core.Model; 3 | 4 | namespace OnlineStore.Api.Tests.TestAssets 5 | { 6 | internal class SimpleCommandNoResult : ICommand 7 | { 8 | } 9 | 10 | internal class SimpleCommandWithResult : ICommand 11 | { 12 | 13 | } 14 | 15 | internal class SimpleCommandCommandResponse : ICommand 16 | { 17 | 18 | } 19 | 20 | internal class SimpleCommandCommandResponseResult : ICommand> 21 | { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api.Tests/TestAssets/TestLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Logging; 4 | using OnlineStore.Api.Commanding; 5 | 6 | namespace OnlineStore.Api.Tests.TestAssets 7 | { 8 | class TestLogger : ILogger 9 | { 10 | public List Messages = new List(); 11 | 12 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 13 | { 14 | Messages.Add(formatter(state, exception)); 15 | } 16 | 17 | public bool IsEnabled(LogLevel logLevel) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | 22 | public IDisposable BeginScope(TState state) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace OnlineStore.Api.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | [Route(""), HttpGet] 8 | [ApiExplorerSettings(IgnoreApi = true)] 9 | public IActionResult Index() 10 | { 11 | return Redirect("/swagger"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Store.Commands; 5 | using Store.Model; 6 | 7 | namespace OnlineStore.Api.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class ProductController : AbstractCommandController 11 | { 12 | public ProductController(ICommandDispatcher dispatcher) : base(dispatcher) 13 | { 14 | 15 | } 16 | 17 | [HttpGet("{productId}")] 18 | [ProducesResponseType(typeof(StoreProduct), 200)] 19 | public async Task Get([FromRoute] GetStoreProductQuery query) => await ExecuteCommand(query); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Exceptions/DispatcherException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OnlineStore.Api.Exceptions 4 | { 5 | public class DispatcherException : Exception 6 | { 7 | public DispatcherException(string message, Exception innerException) : base(message, innerException) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Extensions/CommandResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Core.Model; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class CommandResponseExtensions 7 | { 8 | public static void AddToModelState( 9 | this CommandResponse commandResponse, 10 | ModelStateDictionary modelStateDictionary) 11 | { 12 | foreach (CommandError error in commandResponse.Errors) 13 | { 14 | modelStateDictionary.AddModelError(error.Key ?? "", error.Message); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Extensions/ControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class ControllerExtensions 7 | { 8 | public static Guid GetUserId(this Controller controller) 9 | { 10 | // in reality this would pull the user ID from the claims e.g. 11 | // return Guid.Parse(controller.User.FindFirst("userId").Value); 12 | return Guid.Parse("A9F7EE3A-CB0D-4056-9DB5-AD1CB07D3093"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Extensions/DispatcherExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | using OnlineStore.Api.Exceptions; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class DispatcherExceptionExtensions 7 | { 8 | public static void AddToModelState(this DispatcherException ex, ModelStateDictionary modelStateDictionary) 9 | { 10 | modelStateDictionary.AddModelError("", ex.Message); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Filters/AssignAuthenticatedUserIdActionFilter.cs: -------------------------------------------------------------------------------- 1 | using Core.Model; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | using OnlineStore.Api.Extensions; 5 | 6 | namespace OnlineStore.Api.Filters 7 | { 8 | public class AssignAuthenticatedUserIdActionFilter : IActionFilter 9 | { 10 | public void OnActionExecuting(ActionExecutingContext context) 11 | { 12 | foreach (object parameter in context.ActionArguments.Values) 13 | { 14 | if (parameter is IUserContextCommand userContextCommand) 15 | { 16 | userContextCommand.AuthenticatedUserId = ((Controller) context.Controller).GetUserId(); 17 | } 18 | } 19 | } 20 | 21 | public void OnActionExecuted(ActionExecutedContext context) 22 | { 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Metrics/IMetricCollector.cs: -------------------------------------------------------------------------------- 1 | namespace OnlineStore.Api.Metrics 2 | { 3 | internal interface IMetricCollector 4 | { 5 | void Complete(); 6 | void CompleteWithError(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Metrics/IMetricCollectorFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OnlineStore.Api.Metrics 4 | { 5 | interface IMetricCollectorFactory 6 | { 7 | IMetricCollector Create(); 8 | IMetricCollector Create(Type type); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Metrics/MetricCollectorFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.ApplicationInsights; 3 | 4 | namespace OnlineStore.Api.Metrics 5 | { 6 | internal class MetricCollectorFactory : IMetricCollectorFactory 7 | { 8 | private readonly TelemetryClient _telemetryClient; 9 | 10 | public MetricCollectorFactory(TelemetryClient telemetryClient) 11 | { 12 | _telemetryClient = telemetryClient; 13 | } 14 | 15 | public IMetricCollector Create() 16 | { 17 | return new MetricCollector(typeof(TType).Name, _telemetryClient); 18 | } 19 | 20 | public IMetricCollector Create(Type type) 21 | { 22 | return new MetricCollector(type.Name, _telemetryClient); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace OnlineStore.Api 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("OnlineStore.Api.Tests")] 4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55161/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "OnlineStore": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:55162/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/Swagger/SwaggerAuthenticatedUserIdOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Swashbuckle.AspNetCore.Swagger; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | 5 | namespace OnlineStore.Api.Swagger 6 | { 7 | public class SwaggerAuthenticatedUserIdOperationFilter : IOperationFilter 8 | { 9 | private const string AuthenticatedUserIdProperty = "authenticateduserid"; 10 | 11 | public void Apply(Operation operation, OperationFilterContext context) 12 | { 13 | IParameter authenticatedUserIdParameter = operation.Parameters?.SingleOrDefault(x => x.Name.ToLower() == AuthenticatedUserIdProperty); 14 | if (authenticatedUserIdParameter != null) 15 | { 16 | operation.Parameters.Remove(authenticatedUserIdParameter); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part4-decorator/OnlineStore.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Application/Repositories/IShoppingCartRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ShoppingCart.Application.Repositories 5 | { 6 | internal interface IShoppingCartRepository 7 | { 8 | Task UpdateAsync(Model.ShoppingCart cart); 9 | 10 | Task GetActualOrDefaultAsync(Guid userId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Commands/AddToCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class AddToCartCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid ProductId { get; set; } 12 | 13 | public int Quantity { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Commands/ClearCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class ClearCartCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Commands/GetCartQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class GetCartQuery : ICommand>, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Commands/ShoppingCart.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Model/ShoppingCart.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Model/ShoppingCart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ShoppingCart.Model 5 | { 6 | public class ShoppingCart 7 | { 8 | public ShoppingCart() 9 | { 10 | Items = new List(); 11 | } 12 | 13 | public Guid UserId { get; set; } 14 | 15 | public IReadOnlyCollection Items { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Model/ShoppingCartItem.cs: -------------------------------------------------------------------------------- 1 | using Store.Model; 2 | 3 | namespace ShoppingCart.Model 4 | { 5 | public class ShoppingCartItem 6 | { 7 | public int Quantity { get; set; } 8 | 9 | public StoreProduct Product { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Validation.Tests/ShoppingCart.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Validation/AddToCartCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentValidation; 3 | using ShoppingCart.Commands; 4 | 5 | namespace ShoppingCart.Validation 6 | { 7 | internal class AddToCartCommandValidator : AbstractValidator 8 | { 9 | public AddToCartCommandValidator() 10 | { 11 | RuleFor(c => c.ProductId).NotEqual(Guid.Empty); 12 | RuleFor(c => c.Quantity).GreaterThan(0); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ShoppingCart.Validation.Tests")] -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ShoppingCart.Commands; 4 | 5 | namespace ShoppingCart.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, AddToCartCommandValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part4-decorator/ShoppingCart.Validation/ShoppingCart.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Application/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Store.Application.Handlers; 4 | using Store.Application.Repository; 5 | using Store.Validation; 6 | 7 | namespace Store.Application 8 | { 9 | // ReSharper disable once InconsistentNaming 10 | public static class IServiceCollectionExtensions 11 | { 12 | public static IServiceCollection UseStore(this IServiceCollection serviceCollection, 13 | ICommandRegistry commandRegistry) 14 | { 15 | serviceCollection.AddSingleton(); 16 | 17 | commandRegistry.Register(); 18 | 19 | serviceCollection.RegisterValidators(); 20 | 21 | return serviceCollection; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Application/Repository/IStoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal interface IStoreProductRepository 8 | { 9 | Task GetAsync(Guid productId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Application/Repository/StoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal class StoreProductRepository : IStoreProductRepository 8 | { 9 | public Task GetAsync(Guid productId) 10 | { 11 | return Task.FromResult(new StoreProduct 12 | { 13 | Id = productId, 14 | Name = "A product", 15 | Price = new Random().Next(200,1000) / 100.0 16 | }); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Application/Store.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Commands/GetStoreProductQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | using Store.Model; 5 | 6 | namespace Store.Commands 7 | { 8 | public class GetStoreProductQuery : ICommand> 9 | { 10 | public Guid ProductId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Commands/Store.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Model/Store.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Model/StoreProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Store.Model 4 | { 5 | public class StoreProduct 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public double Price { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Validation.Tests/Store.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Store.Validation.Tests")] -------------------------------------------------------------------------------- /Part4-decorator/Store.Validation/GetStoreProductQueryValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentValidation; 3 | using Store.Commands; 4 | 5 | namespace Store.Validation 6 | { 7 | internal class GetStoreProductQueryValidator : AbstractValidator 8 | { 9 | public GetStoreProductQueryValidator() 10 | { 11 | RuleFor(c => c.ProductId).NotEqual(Guid.Empty); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Store.Commands; 4 | 5 | namespace Store.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, GetStoreProductQueryValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part4-decorator/Store.Validation/Store.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Application/Repositories/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Checkout.Model; 4 | 5 | namespace Checkout.Application.Repositories 6 | { 7 | internal interface IOrderRepository 8 | { 9 | Task CreateAsync(Order order); 10 | Task UpdateAsync(Order order); 11 | Task GetAsync(Guid orderId); 12 | } 13 | } -------------------------------------------------------------------------------- /Part4-framework/Checkout.Application/Repositories/OrderRepositoryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Application.Repositories 4 | { 5 | internal class OrderRepositoryException : Exception 6 | { 7 | public OrderRepositoryException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Application/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Checkout.Application.Handlers; 3 | using Checkout.Application.Repositories; 4 | using Checkout.Validation; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Checkout.Application 8 | { 9 | public static class ServiceCollectionExtensions 10 | { 11 | public static IServiceCollection UseCheckout(this IServiceCollection serviceCollection, 12 | ICommandRegistry registry) 13 | { 14 | serviceCollection.AddSingleton(); 15 | 16 | registry.Register(); 17 | registry.Register(); 18 | 19 | serviceCollection.RegisterValidators(); 20 | 21 | return serviceCollection; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Commands/Checkout.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Commands/CreateOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Checkout.Model; 4 | using Core.Model; 5 | 6 | namespace Checkout.Commands 7 | { 8 | public class CreateOrderCommand : ICommand>, IUserContextCommand 9 | { 10 | public Guid AuthenticatedUserId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Commands/MakePaymentCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace Checkout.Commands 6 | { 7 | public class MakePaymentCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid OrderId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Model/Checkout.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Model/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Checkout.Model 5 | { 6 | public class Order 7 | { 8 | public Guid Id { get; set; } 9 | 10 | public Guid UserId { get; set; } 11 | 12 | public DateTime CreatedAtUtc { get; set; } 13 | 14 | public IReadOnlyCollection OrderItems { get; set; } 15 | 16 | public double PercentageDiscountApplied { get; set; } 17 | 18 | public double Total { get; set; } 19 | 20 | public bool PaymentMade { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Model/OrderItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Model 4 | { 5 | public class OrderItem 6 | { 7 | public Guid ProductId { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public int Quantity { get; set; } 12 | 13 | public double Price { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Validation.Tests/Checkout.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Checkout.Validation.Tests")] 4 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Validation/Checkout.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Checkout.Commands; 2 | using FluentValidation; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Checkout.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, MakePaymentCommandValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part4-framework/Checkout.Validation/MakePaymentCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Checkout.Commands; 3 | using FluentValidation; 4 | 5 | namespace Checkout.Validation 6 | { 7 | class MakePaymentCommandValidator : AbstractValidator 8 | { 9 | public MakePaymentCommandValidator() 10 | { 11 | RuleFor(c => c.OrderId).NotEqual(Guid.Empty); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part4-framework/Core.Model/ApplicationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public class ApplicationException : Exception 6 | { 7 | public ApplicationException(string message) : base(message) 8 | { 9 | } 10 | 11 | public ApplicationException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-framework/Core.Model/CommandError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Model 6 | { 7 | public class CommandError 8 | { 9 | public CommandError(string message) 10 | { 11 | Key = ""; 12 | Message = message; 13 | } 14 | 15 | public CommandError(string key, string message) 16 | { 17 | Key = key; 18 | Message = message; 19 | } 20 | 21 | public string Key { get; } 22 | 23 | public string Message { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Part4-framework/Core.Model/Core.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part4-framework/Core.Model/IUserContextCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public interface IUserContextCommand 6 | { 7 | Guid AuthenticatedUserId { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api.Tests/OnlineStore.Api.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api.Tests/TestAssets/SimpleCommand.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Core.Model; 3 | 4 | namespace OnlineStore.Api.Tests.TestAssets 5 | { 6 | internal class SimpleCommandNoResult : ICommand 7 | { 8 | } 9 | 10 | internal class SimpleCommandWithResult : ICommand 11 | { 12 | 13 | } 14 | 15 | internal class SimpleCommandCommandResponse : ICommand 16 | { 17 | 18 | } 19 | 20 | internal class SimpleCommandCommandResponseResult : ICommand> 21 | { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Commanding/AuditItemUserIdEnricher.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace OnlineStore.Api.Commanding 6 | { 7 | public class AuditItemUserIdEnricher : IAuditItemEnricher 8 | { 9 | public void Enrich(Dictionary properties, ICommand command, ICommandDispatchContext context) 10 | { 11 | if (command is IUserContextCommand userContextCommand) 12 | { 13 | properties["UserId"] = userContextCommand.AuthenticatedUserId.ToString(); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Commanding/CommandExecutionExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AzureFromTheTrenches.Commanding.Abstractions; 4 | using OnlineStore.Api.Exceptions; 5 | 6 | namespace OnlineStore.Api.Commanding 7 | { 8 | public class CommandExecutionExceptionHandler : ICommandExecutionExceptionHandler 9 | { 10 | public Task HandleException(Exception ex, object handler, int handlerExecutionIndex, ICommand command, 11 | ICommandDispatchContext dispatchContext) 12 | { 13 | throw new DispatcherException($"Error occurred performing operation {command.GetType().Name}", ex); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace OnlineStore.Api.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | [Route(""), HttpGet] 8 | [ApiExplorerSettings(IgnoreApi = true)] 9 | public IActionResult Index() 10 | { 11 | return Redirect("/swagger"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Store.Commands; 5 | using Store.Model; 6 | 7 | namespace OnlineStore.Api.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class ProductController : AbstractCommandController 11 | { 12 | public ProductController(ICommandDispatcher dispatcher) : base(dispatcher) 13 | { 14 | 15 | } 16 | 17 | [HttpGet("{productId}")] 18 | [ProducesResponseType(typeof(StoreProduct), 200)] 19 | public async Task Get([FromRoute] GetStoreProductQuery query) => await ExecuteCommand(query); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Exceptions/DispatcherException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OnlineStore.Api.Exceptions 4 | { 5 | public class DispatcherException : Exception 6 | { 7 | public DispatcherException(string message, Exception innerException) : base(message, innerException) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Extensions/CommandResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Core.Model; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class CommandResponseExtensions 7 | { 8 | public static void AddToModelState( 9 | this CommandResponse commandResponse, 10 | ModelStateDictionary modelStateDictionary) 11 | { 12 | foreach (CommandError error in commandResponse.Errors) 13 | { 14 | modelStateDictionary.AddModelError(error.Key ?? "", error.Message); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Extensions/ControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class ControllerExtensions 7 | { 8 | public static Guid GetUserId(this Controller controller) 9 | { 10 | // in reality this would pull the user ID from the claims e.g. 11 | // return Guid.Parse(controller.User.FindFirst("userId").Value); 12 | return Guid.Parse("A9F7EE3A-CB0D-4056-9DB5-AD1CB07D3093"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Extensions/DispatcherExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | using OnlineStore.Api.Exceptions; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class DispatcherExceptionExtensions 7 | { 8 | public static void AddToModelState(this DispatcherException ex, ModelStateDictionary modelStateDictionary) 9 | { 10 | modelStateDictionary.AddModelError("", ex.Message); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Filters/AssignAuthenticatedUserIdActionFilter.cs: -------------------------------------------------------------------------------- 1 | using Core.Model; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | using OnlineStore.Api.Extensions; 5 | 6 | namespace OnlineStore.Api.Filters 7 | { 8 | public class AssignAuthenticatedUserIdActionFilter : IActionFilter 9 | { 10 | public void OnActionExecuting(ActionExecutingContext context) 11 | { 12 | foreach (object parameter in context.ActionArguments.Values) 13 | { 14 | if (parameter is IUserContextCommand userContextCommand) 15 | { 16 | userContextCommand.AuthenticatedUserId = ((Controller) context.Controller).GetUserId(); 17 | } 18 | } 19 | } 20 | 21 | public void OnActionExecuted(ActionExecutedContext context) 22 | { 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Metrics/IMetricCollector.cs: -------------------------------------------------------------------------------- 1 | namespace OnlineStore.Api.Metrics 2 | { 3 | internal interface IMetricCollector 4 | { 5 | void Record(string type, long elapsedMilliseconds); 6 | void RecordWithError(string type, long elapsedMilliseconds); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace OnlineStore.Api 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("OnlineStore.Api.Tests")] 4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55161/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "OnlineStore": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:55162/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/Swagger/SwaggerAuthenticatedUserIdOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Swashbuckle.AspNetCore.Swagger; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | 5 | namespace OnlineStore.Api.Swagger 6 | { 7 | public class SwaggerAuthenticatedUserIdOperationFilter : IOperationFilter 8 | { 9 | private const string AuthenticatedUserIdProperty = "authenticateduserid"; 10 | 11 | public void Apply(Operation operation, OperationFilterContext context) 12 | { 13 | IParameter authenticatedUserIdParameter = operation.Parameters?.SingleOrDefault(x => x.Name.ToLower() == AuthenticatedUserIdProperty); 14 | if (authenticatedUserIdParameter != null) 15 | { 16 | operation.Parameters.Remove(authenticatedUserIdParameter); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part4-framework/OnlineStore.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Application/Repositories/IShoppingCartRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ShoppingCart.Application.Repositories 5 | { 6 | internal interface IShoppingCartRepository 7 | { 8 | Task UpdateAsync(Model.ShoppingCart cart); 9 | 10 | Task GetActualOrDefaultAsync(Guid userId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Commands/AddToCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class AddToCartCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid ProductId { get; set; } 12 | 13 | public int Quantity { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Commands/ClearCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class ClearCartCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Commands/GetCartQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class GetCartQuery : ICommand>, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Commands/ShoppingCart.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Model/ShoppingCart.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Model/ShoppingCart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ShoppingCart.Model 5 | { 6 | public class ShoppingCart 7 | { 8 | public ShoppingCart() 9 | { 10 | Items = new List(); 11 | } 12 | 13 | public Guid UserId { get; set; } 14 | 15 | public IReadOnlyCollection Items { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Model/ShoppingCartItem.cs: -------------------------------------------------------------------------------- 1 | using Store.Model; 2 | 3 | namespace ShoppingCart.Model 4 | { 5 | public class ShoppingCartItem 6 | { 7 | public int Quantity { get; set; } 8 | 9 | public StoreProduct Product { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Validation.Tests/ShoppingCart.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Validation/AddToCartCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentValidation; 3 | using ShoppingCart.Commands; 4 | 5 | namespace ShoppingCart.Validation 6 | { 7 | internal class AddToCartCommandValidator : AbstractValidator 8 | { 9 | public AddToCartCommandValidator() 10 | { 11 | RuleFor(c => c.ProductId).NotEqual(Guid.Empty); 12 | RuleFor(c => c.Quantity).GreaterThan(0); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ShoppingCart.Validation.Tests")] -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ShoppingCart.Commands; 4 | 5 | namespace ShoppingCart.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, AddToCartCommandValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part4-framework/ShoppingCart.Validation/ShoppingCart.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-framework/Store.Application/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Store.Application.Handlers; 4 | using Store.Application.Repository; 5 | using Store.Validation; 6 | 7 | namespace Store.Application 8 | { 9 | // ReSharper disable once InconsistentNaming 10 | public static class IServiceCollectionExtensions 11 | { 12 | public static IServiceCollection UseStore(this IServiceCollection serviceCollection, 13 | ICommandRegistry commandRegistry) 14 | { 15 | serviceCollection.AddSingleton(); 16 | 17 | commandRegistry.Register(); 18 | 19 | serviceCollection.RegisterValidators(); 20 | 21 | return serviceCollection; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part4-framework/Store.Application/Repository/IStoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal interface IStoreProductRepository 8 | { 9 | Task GetAsync(Guid productId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part4-framework/Store.Application/Repository/StoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal class StoreProductRepository : IStoreProductRepository 8 | { 9 | public Task GetAsync(Guid productId) 10 | { 11 | return Task.FromResult(new StoreProduct 12 | { 13 | Id = productId, 14 | Name = "A product", 15 | Price = new Random().Next(200,1000) / 100.0 16 | }); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Part4-framework/Store.Application/Store.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Part4-framework/Store.Commands/GetStoreProductQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | using Store.Model; 5 | 6 | namespace Store.Commands 7 | { 8 | public class GetStoreProductQuery : ICommand> 9 | { 10 | public Guid ProductId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part4-framework/Store.Commands/Store.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part4-framework/Store.Model/Store.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part4-framework/Store.Model/StoreProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Store.Model 4 | { 5 | public class StoreProduct 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public double Price { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part4-framework/Store.Validation.Tests/Store.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Part4-framework/Store.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Store.Validation.Tests")] -------------------------------------------------------------------------------- /Part4-framework/Store.Validation/GetStoreProductQueryValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentValidation; 3 | using Store.Commands; 4 | 5 | namespace Store.Validation 6 | { 7 | internal class GetStoreProductQueryValidator : AbstractValidator 8 | { 9 | public GetStoreProductQueryValidator() 10 | { 11 | RuleFor(c => c.ProductId).NotEqual(Guid.Empty); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part4-framework/Store.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Store.Commands; 4 | 5 | namespace Store.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, GetStoreProductQueryValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part4-framework/Store.Validation/Store.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part5/Checkout.Application/Repositories/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Checkout.Model; 4 | 5 | namespace Checkout.Application.Repositories 6 | { 7 | internal interface IOrderRepository 8 | { 9 | Task CreateAsync(Order order); 10 | Task UpdateAsync(Order order); 11 | Task GetAsync(Guid orderId); 12 | } 13 | } -------------------------------------------------------------------------------- /Part5/Checkout.Application/Repositories/OrderRepositoryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Application.Repositories 4 | { 5 | internal class OrderRepositoryException : Exception 6 | { 7 | public OrderRepositoryException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part5/Checkout.Application/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Checkout.Application.Handlers; 3 | using Checkout.Application.Repositories; 4 | using Checkout.Validation; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace Checkout.Application 8 | { 9 | public static class ServiceCollectionExtensions 10 | { 11 | public static IServiceCollection UseCheckout(this IServiceCollection serviceCollection, 12 | ICommandRegistry registry) 13 | { 14 | serviceCollection.AddSingleton(); 15 | 16 | registry.Register(); 17 | registry.Register(); 18 | 19 | serviceCollection.RegisterValidators(); 20 | 21 | return serviceCollection; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part5/Checkout.Commands/Checkout.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part5/Checkout.Commands/CreateOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Checkout.Model; 4 | using Core.Model; 5 | 6 | namespace Checkout.Commands 7 | { 8 | public class CreateOrderCommand : ICommand>, IUserContextCommand 9 | { 10 | public Guid AuthenticatedUserId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part5/Checkout.Commands/MakePaymentCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace Checkout.Commands 6 | { 7 | public class MakePaymentCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid OrderId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part5/Checkout.Model/Checkout.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part5/Checkout.Model/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Checkout.Model 5 | { 6 | public class Order 7 | { 8 | public Guid Id { get; set; } 9 | 10 | public Guid UserId { get; set; } 11 | 12 | public DateTime CreatedAtUtc { get; set; } 13 | 14 | public IReadOnlyCollection OrderItems { get; set; } 15 | 16 | public double PercentageDiscountApplied { get; set; } 17 | 18 | public double Total { get; set; } 19 | 20 | public bool PaymentMade { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Part5/Checkout.Model/OrderItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Checkout.Model 4 | { 5 | public class OrderItem 6 | { 7 | public Guid ProductId { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public int Quantity { get; set; } 12 | 13 | public double Price { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part5/Checkout.Validation.Tests/Checkout.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Part5/Checkout.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Checkout.Validation.Tests")] 4 | -------------------------------------------------------------------------------- /Part5/Checkout.Validation/Checkout.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part5/Checkout.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Checkout.Commands; 2 | using FluentValidation; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Checkout.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, MakePaymentCommandValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part5/Checkout.Validation/MakePaymentCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Checkout.Commands; 3 | using FluentValidation; 4 | 5 | namespace Checkout.Validation 6 | { 7 | class MakePaymentCommandValidator : AbstractValidator 8 | { 9 | public MakePaymentCommandValidator() 10 | { 11 | RuleFor(c => c.OrderId).NotEqual(Guid.Empty); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part5/Core.Commanding/Commanding/AuditItemUserIdEnricher.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace Core.Commanding.Commanding 6 | { 7 | public class AuditItemUserIdEnricher : IAuditItemEnricher 8 | { 9 | public void Enrich(Dictionary properties, ICommand command, ICommandDispatchContext context) 10 | { 11 | if (command is IUserContextCommand userContextCommand) 12 | { 13 | properties["UserId"] = userContextCommand.AuthenticatedUserId.ToString(); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Part5/Core.Commanding/Commanding/CommandExecutionExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AzureFromTheTrenches.Commanding.Abstractions; 4 | using Core.Commanding.Exceptions; 5 | 6 | namespace Core.Commanding.Commanding 7 | { 8 | public class CommandExecutionExceptionHandler : ICommandExecutionExceptionHandler 9 | { 10 | public Task HandleException(Exception ex, object handler, int handlerExecutionIndex, ICommand command, 11 | ICommandDispatchContext dispatchContext) 12 | { 13 | throw new DispatcherException($"Error occurred performing operation {command.GetType().Name}", ex); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part5/Core.Commanding/Core.Commanding.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Part5/Core.Commanding/Exceptions/DispatcherException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Commanding.Exceptions 4 | { 5 | public class DispatcherException : Exception 6 | { 7 | public DispatcherException(string message, Exception innerException) : base(message, innerException) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part5/Core.Commanding/Metrics/IMetricCollector.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Commanding.Metrics 2 | { 3 | internal interface IMetricCollector 4 | { 5 | void Record(string type, long elapsedMilliseconds); 6 | void RecordWithError(string type, long elapsedMilliseconds); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Part5/Core.Commanding/Metrics/MetricCollector.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Commanding.Metrics 2 | { 3 | public class MetricCollector : IMetricCollector 4 | { 5 | 6 | public MetricCollector() 7 | { 8 | //_telemetryClient = telemetryClient; 9 | } 10 | 11 | public void Record(string type, long elapsedMilliseconds) 12 | { 13 | //_telemetryClient.TrackMetric(type, elapsedMilliseconds, new Dictionary{ {"success", "true"} }); 14 | } 15 | 16 | public void RecordWithError(string type, long elapsedMilliseconds) 17 | { 18 | //_telemetryClient.TrackMetric(type, elapsedMilliseconds, new Dictionary { { "success", "false" } }); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part5/Core.Model/ApplicationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public class ApplicationException : Exception 6 | { 7 | public ApplicationException(string message) : base(message) 8 | { 9 | } 10 | 11 | public ApplicationException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part5/Core.Model/CommandError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Core.Model 6 | { 7 | public class CommandError 8 | { 9 | public CommandError(string message) 10 | { 11 | Key = ""; 12 | Message = message; 13 | } 14 | 15 | public CommandError(string key, string message) 16 | { 17 | Key = key; 18 | Message = message; 19 | } 20 | 21 | public string Key { get; } 22 | 23 | public string Message { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Part5/Core.Model/Core.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part5/Core.Model/IUserContextCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Model 4 | { 5 | public interface IUserContextCommand 6 | { 7 | Guid AuthenticatedUserId { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api.Tests/OnlineStore.Api.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api.Tests/TestAssets/SimpleCommand.cs: -------------------------------------------------------------------------------- 1 | using AzureFromTheTrenches.Commanding.Abstractions; 2 | using Core.Model; 3 | 4 | namespace OnlineStore.Api.Tests.TestAssets 5 | { 6 | internal class SimpleCommandNoResult : ICommand 7 | { 8 | } 9 | 10 | internal class SimpleCommandWithResult : ICommand 11 | { 12 | 13 | } 14 | 15 | internal class SimpleCommandCommandResponse : ICommand 16 | { 17 | 18 | } 19 | 20 | internal class SimpleCommandCommandResponseResult : ICommand> 21 | { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Binders/AuthenticatedUserIdAwareBodyModelBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Core.Model; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding; 5 | 6 | namespace OnlineStore.Api.Binders 7 | { 8 | public class AuthenticatedUserIdAwareBodyModelBinder : IModelBinder 9 | { 10 | private readonly IModelBinder _decoratedBinder; 11 | 12 | public AuthenticatedUserIdAwareBodyModelBinder(IModelBinder decoratedBinder) 13 | { 14 | _decoratedBinder = decoratedBinder; 15 | } 16 | 17 | public async Task BindModelAsync(ModelBindingContext bindingContext) 18 | { 19 | await _decoratedBinder.BindModelAsync(bindingContext); 20 | if (bindingContext.Result.Model is IUserContextCommand command) 21 | { 22 | command.AuthenticatedUserId = Guid.Empty; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace OnlineStore.Api.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | [Route(""), HttpGet] 8 | [ApiExplorerSettings(IgnoreApi = true)] 9 | public IActionResult Index() 10 | { 11 | return Redirect("/swagger"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Store.Commands; 5 | using Store.Model; 6 | 7 | namespace OnlineStore.Api.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class ProductController : AbstractCommandController 11 | { 12 | public ProductController(ICommandDispatcher dispatcher) : base(dispatcher) 13 | { 14 | 15 | } 16 | 17 | [HttpGet("{productId}")] 18 | [ProducesResponseType(typeof(StoreProduct), 200)] 19 | public async Task Get([FromRoute] GetStoreProductQuery query) => await ExecuteCommand(query); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Extensions/CommandResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Core.Model; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class CommandResponseExtensions 7 | { 8 | public static void AddToModelState( 9 | this CommandResponse commandResponse, 10 | ModelStateDictionary modelStateDictionary) 11 | { 12 | foreach (CommandError error in commandResponse.Errors) 13 | { 14 | modelStateDictionary.AddModelError(error.Key ?? "", error.Message); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Extensions/ControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class ControllerExtensions 7 | { 8 | public static Guid GetUserId(this Controller controller) 9 | { 10 | // in reality this would pull the user ID from the claims e.g. 11 | // return Guid.Parse(controller.User.FindFirst("userId").Value); 12 | return Guid.Parse("A9F7EE3A-CB0D-4056-9DB5-AD1CB07D3093"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Extensions/DispatcherExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Core.Commanding.Exceptions; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | 4 | namespace OnlineStore.Api.Extensions 5 | { 6 | public static class DispatcherExceptionExtensions 7 | { 8 | public static void AddToModelState(this DispatcherException ex, ModelStateDictionary modelStateDictionary) 9 | { 10 | modelStateDictionary.AddModelError("", ex.Message); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Filters/AssignAuthenticatedUserIdActionFilter.cs: -------------------------------------------------------------------------------- 1 | using Core.Model; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | using OnlineStore.Api.Extensions; 5 | 6 | namespace OnlineStore.Api.Filters 7 | { 8 | public class AssignAuthenticatedUserIdActionFilter : IActionFilter 9 | { 10 | public void OnActionExecuting(ActionExecutingContext context) 11 | { 12 | foreach (object parameter in context.ActionArguments.Values) 13 | { 14 | if (parameter is IUserContextCommand userContextCommand) 15 | { 16 | userContextCommand.AuthenticatedUserId = ((Controller) context.Controller).GetUserId(); 17 | } 18 | } 19 | } 20 | 21 | public void OnActionExecuted(ActionExecutedContext context) 22 | { 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace OnlineStore.Api 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("OnlineStore.Api.Tests")] 4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55161/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "OnlineStore": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:55162/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/Swagger/SwaggerAuthenticatedUserIdOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Swashbuckle.AspNetCore.Swagger; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | 5 | namespace OnlineStore.Api.Swagger 6 | { 7 | public class SwaggerAuthenticatedUserIdOperationFilter : IOperationFilter 8 | { 9 | private const string AuthenticatedUserIdProperty = "authenticateduserid"; 10 | 11 | public void Apply(Operation operation, OperationFilterContext context) 12 | { 13 | IParameter authenticatedUserIdParameter = operation.Parameters?.SingleOrDefault(x => x.Name.ToLower() == AuthenticatedUserIdProperty); 14 | if (authenticatedUserIdParameter != null) 15 | { 16 | operation.Parameters.Remove(authenticatedUserIdParameter); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Part5/OnlineStore.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Application/Repositories/IShoppingCartRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ShoppingCart.Application.Repositories 5 | { 6 | internal interface IShoppingCartRepository 7 | { 8 | Task UpdateAsync(Model.ShoppingCart cart); 9 | 10 | Task GetActualOrDefaultAsync(Guid userId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Commands/AddToCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class AddToCartCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | 11 | public Guid ProductId { get; set; } 12 | 13 | public int Quantity { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Commands/ClearCartCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class ClearCartCommand : ICommand, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Commands/GetCartQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | 5 | namespace ShoppingCart.Commands 6 | { 7 | public class GetCartQuery : ICommand>, IUserContextCommand 8 | { 9 | public Guid AuthenticatedUserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Commands/ShoppingCart.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Model/ShoppingCart.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Model/ShoppingCart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ShoppingCart.Model 5 | { 6 | public class ShoppingCart 7 | { 8 | public ShoppingCart() 9 | { 10 | Items = new List(); 11 | } 12 | 13 | public Guid UserId { get; set; } 14 | 15 | public IReadOnlyCollection Items { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Model/ShoppingCartItem.cs: -------------------------------------------------------------------------------- 1 | using Store.Model; 2 | 3 | namespace ShoppingCart.Model 4 | { 5 | public class ShoppingCartItem 6 | { 7 | public int Quantity { get; set; } 8 | 9 | public StoreProduct Product { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Validation.Tests/ShoppingCart.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Validation/AddToCartCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentValidation; 3 | using ShoppingCart.Commands; 4 | 5 | namespace ShoppingCart.Validation 6 | { 7 | internal class AddToCartCommandValidator : AbstractValidator 8 | { 9 | public AddToCartCommandValidator() 10 | { 11 | RuleFor(c => c.ProductId).NotEqual(Guid.Empty); 12 | RuleFor(c => c.Quantity).GreaterThan(0); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ShoppingCart.Validation.Tests")] -------------------------------------------------------------------------------- /Part5/ShoppingCart.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ShoppingCart.Commands; 4 | 5 | namespace ShoppingCart.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, AddToCartCommandValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part5/ShoppingCart.Validation/ShoppingCart.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part5/Store.Application/Repository/IStoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal interface IStoreProductRepository 8 | { 9 | Task GetAsync(Guid productId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Part5/Store.Application/Repository/StoreProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Store.Model; 4 | 5 | namespace Store.Application.Repository 6 | { 7 | internal class StoreProductRepository : IStoreProductRepository 8 | { 9 | public Task GetAsync(Guid productId) 10 | { 11 | return Task.FromResult(new StoreProduct 12 | { 13 | Id = productId, 14 | Name = "A product", 15 | Price = new Random().Next(200,1000) / 100.0 16 | }); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Part5/Store.Application/Store.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Part5/Store.Commands/GetStoreProductQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFromTheTrenches.Commanding.Abstractions; 3 | using Core.Model; 4 | using Store.Model; 5 | 6 | namespace Store.Commands 7 | { 8 | public class GetStoreProductQuery : ICommand> 9 | { 10 | public Guid ProductId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Part5/Store.Commands/Store.Commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Part5/Store.Functions/host.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /Part5/Store.Model/Store.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Part5/Store.Model/StoreProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Store.Model 4 | { 5 | public class StoreProduct 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public double Price { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Part5/Store.Validation.Tests/Store.Validation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Part5/Store.Validation/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Store.Validation.Tests")] -------------------------------------------------------------------------------- /Part5/Store.Validation/GetStoreProductQueryValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentValidation; 3 | using Store.Commands; 4 | 5 | namespace Store.Validation 6 | { 7 | internal class GetStoreProductQueryValidator : AbstractValidator 8 | { 9 | public GetStoreProductQueryValidator() 10 | { 11 | RuleFor(c => c.ProductId).NotEqual(Guid.Empty); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Part5/Store.Validation/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Store.Commands; 4 | 5 | namespace Store.Validation 6 | { 7 | // ReSharper disable once InconsistentNaming 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection) 11 | { 12 | serviceCollection.AddTransient, GetStoreProductQueryValidator>(); 13 | return serviceCollection; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Part5/Store.Validation/Store.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------