├── presentation └── OOPsIDidItAgain.pptx ├── src ├── OOPsIDidItAgain.04.OOifying.Web │ ├── Handlers │ │ ├── IRequest.cs │ │ ├── IRequestHandler.cs │ │ └── RequestHandlerLoggingDecorator.cs │ ├── Services │ │ └── INotifier.cs │ ├── Domain │ │ ├── IItemRepository.cs │ │ ├── ItemSaleRule │ │ │ ├── IItemSaleRule.cs │ │ │ ├── NoopItemSaleRule.cs │ │ │ ├── CompositeItemSaleRule.cs │ │ │ ├── MaximumQuantityPerSaleRule.cs │ │ │ └── MinimumTimeOfDayForSaleRule.cs │ │ ├── ICartRepository.cs │ │ ├── Item.cs │ │ ├── IItemSaleRuleRepository.cs │ │ ├── PostAddItemToCartListeners │ │ │ ├── IPostAddItemToCartListener.cs │ │ │ ├── CompositePostAddItemToCartListener.cs │ │ │ └── WatchlistNotifierListener.cs │ │ ├── CartItem.cs │ │ └── Cart.cs │ ├── OOPsIDidItAgain.04.OOifying.Web.csproj │ ├── Features │ │ └── Carts │ │ │ ├── CartItemDto.cs │ │ │ ├── CartDto.cs │ │ │ ├── AddItemToCartDto.cs │ │ │ ├── AddItemToCartEndpoint.cs │ │ │ └── AddItemToCart.cs │ ├── Program.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── Exceptions │ │ ├── NotFoundException.cs │ │ └── ValidationException.cs ├── OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web │ ├── Handlers │ │ ├── IRequest.cs │ │ ├── IRequestHandler.cs │ │ └── RequestHandlerLoggingDecorator.cs │ ├── Features │ │ ├── Carts │ │ │ ├── UpdateItemInCartDto.cs │ │ │ ├── CartItemDto.cs │ │ │ ├── CartDto.cs │ │ │ ├── AddItemToCartDto.cs │ │ │ ├── AddItemToCartEndpoint.cs │ │ │ └── AddItemToCart.cs │ │ └── ResultExtensions.cs │ ├── Domain │ │ ├── IItemRepository.cs │ │ ├── ItemSaleRule │ │ │ ├── IItemSaleRule.cs │ │ │ ├── NoopItemSaleRule.cs │ │ │ ├── CompositeItemSaleRule.cs │ │ │ ├── MaximumQuantityPerSaleRule.cs │ │ │ └── MinimumTimeOfDayForSaleRule.cs │ │ ├── PostAddItemToCartListeners │ │ │ ├── IPostAddItemToCartListener.cs │ │ │ ├── CompositePostAddItemToCartListener.cs │ │ │ └── WatchlistNotifierListener.cs │ │ ├── IItemSaleRuleRepository.cs │ │ ├── Item.cs │ │ ├── DomainException.cs │ │ ├── ICartRepository.cs │ │ ├── CartId.cs │ │ ├── CartItem.cs │ │ ├── ItemId.cs │ │ ├── ErrorDetail.cs │ │ └── Cart.cs │ ├── Shared │ │ ├── IStronglyTypedId.cs │ │ ├── Unit.cs │ │ ├── Require.cs │ │ └── Maybe.cs │ ├── appsettings.Development.json │ ├── Services │ │ └── INotifier.cs │ ├── appsettings.json │ ├── OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web.csproj │ ├── Program.cs │ └── Middlewares │ │ ├── ResultMappingErrorVisitor.cs │ │ └── ExceptionHandlingMiddleware.cs ├── OOPsIDidItAgain.06.MinimizingExceptions.Web │ ├── Handlers │ │ ├── IRequest.cs │ │ ├── IRequestHandler.cs │ │ └── RequestHandlerLoggingDecorator.cs │ ├── Domain │ │ ├── IItemRepository.cs │ │ ├── PostAddItemToCartListeners │ │ │ ├── IPostAddItemToCartListener.cs │ │ │ ├── CompositePostAddItemToCartListener.cs │ │ │ └── WatchlistNotifierListener.cs │ │ ├── IItemSaleRuleRepository.cs │ │ ├── ItemSaleRule │ │ │ ├── IItemSaleRule.cs │ │ │ ├── NoopItemSaleRule.cs │ │ │ ├── MaximumQuantityPerSaleRule.cs │ │ │ ├── MinimumTimeOfDayForSaleRule.cs │ │ │ └── CompositeItemSaleRule.cs │ │ ├── Item.cs │ │ ├── ICartRepository.cs │ │ ├── CartItem.cs │ │ ├── CartId.cs │ │ ├── ItemId.cs │ │ └── Cart.cs │ ├── Features │ │ ├── Carts │ │ │ ├── CartItemDto.cs │ │ │ ├── CartDto.cs │ │ │ ├── AddItemToCartDto.cs │ │ │ ├── AddItemToCartEndpoint.cs │ │ │ ├── GetCartEndpoint.cs │ │ │ ├── GetCart.cs │ │ │ ├── AddItemToCart2.cs │ │ │ └── AddItemToCart.cs │ │ └── ResultExtensions.cs │ ├── Shared │ │ ├── Unit.cs │ │ ├── IStronglyTypedId.cs │ │ ├── MaybeExtensions.cs │ │ ├── Require.cs │ │ ├── Either.cs │ │ ├── Maybe.cs │ │ ├── Error.cs │ │ ├── StronglyTypedId.cs │ │ └── EitherExtensions.cs │ ├── appsettings.Development.json │ ├── Services │ │ └── INotifier.cs │ ├── appsettings.json │ ├── OOPsIDidItAgain.06.MinimizingExceptions.Web.csproj │ ├── Infrastructure │ │ ├── LoggingNotifier.cs │ │ ├── InMemoryItemRepository.cs │ │ ├── InMemoryItemSaleRuleRepository.cs │ │ └── InMemoryCartRepository.cs │ ├── ModelBinding │ │ └── StronglyTypedIdJsonConverter.cs │ └── Program.cs ├── OOPsIDidItAgain.02.SuperService.Web │ ├── Data │ │ ├── IItemRepository.cs │ │ ├── CartItem.cs │ │ ├── Cart.cs │ │ ├── ICartRepository.cs │ │ └── Item.cs │ ├── Services │ │ ├── INotifier.cs │ │ ├── ICartsService.cs │ │ └── CartsService.cs │ ├── Models │ │ ├── UpdateItemInCart.cs │ │ ├── CartModel.cs │ │ ├── AddItemToCartModel.cs │ │ └── CartItemModel.cs │ ├── Program.cs │ ├── OOPsIDidItAgain.02.SuperService.Web.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Exceptions │ │ ├── NotFoundException.cs │ │ └── ValidationException.cs │ └── Controllers │ │ └── CartsController.cs ├── OOPsIDidItAgain.03.IndividualRequestHandlers.Web │ ├── Handlers │ │ ├── IRequest.cs │ │ ├── IRequestHandler.cs │ │ └── RequestHandlerLoggingDecorator.cs │ ├── Data │ │ ├── IItemRepository.cs │ │ ├── CartItem.cs │ │ ├── Cart.cs │ │ ├── ICartRepository.cs │ │ └── Item.cs │ ├── Services │ │ └── INotifier.cs │ ├── appsettings.Development.json │ ├── Features │ │ └── Carts │ │ │ ├── CartItemDto.cs │ │ │ ├── AddItemToCartDto.cs │ │ │ ├── CartsController.cs │ │ │ ├── AddItemToCartEndpoint.cs │ │ │ └── AddItemToCart.cs │ ├── appsettings.json │ ├── OOPsIDidItAgain.03.IndividualRequestHandlers.Web.csproj │ ├── Exceptions │ │ ├── NotFoundException.cs │ │ └── ValidationException.cs │ └── Program.cs ├── OOPsIDidItAgain.01.SuperController.Web │ ├── Data │ │ ├── IItemRepository.cs │ │ ├── CartItem.cs │ │ ├── Cart.cs │ │ ├── ICartRepository.cs │ │ └── Item.cs │ ├── Services │ │ └── INotifier.cs │ ├── Models │ │ ├── UpdateItemInCart.cs │ │ ├── CartItemModel.cs │ │ ├── AddItemToCartModel.cs │ │ └── CartModel.cs │ ├── Program.cs │ ├── appsettings.Development.json │ ├── OOPsIDidItAgain.01.SuperController.Web.csproj │ ├── appsettings.json │ └── Controllers │ │ └── CartsController.cs └── Directory.Build.props ├── sample-requests.http ├── LICENSE ├── readme.md ├── .gitignore ├── .editorconfig └── OOPsIDidItAgain.sln /presentation/OOPsIDidItAgain.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaofbantunes/OOPsIDidItAgain/HEAD/presentation/OOPsIDidItAgain.pptx -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Handlers/IRequest.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Handlers; 2 | 3 | public interface IRequest 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Handlers/IRequest.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Handlers; 2 | 3 | public interface IRequest 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Services/INotifier.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Services; 2 | 3 | public interface INotifier 4 | { 5 | void Notify(string itemId); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Handlers/IRequest.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Handlers; 2 | 3 | public interface IRequest 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Data/IItemRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Data; 2 | 3 | public interface IItemRepository 4 | { 5 | Item Get(string itemId); 6 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Services/INotifier.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Services; 2 | 3 | public interface INotifier 4 | { 5 | void Notify(string itemId); 6 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Handlers/IRequest.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Handlers; 2 | 3 | public interface IRequest 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/IItemRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain; 2 | 3 | public interface IItemRepository 4 | { 5 | Item Get(string itemId); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Data/IItemRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Data; 2 | 3 | public interface IItemRepository 4 | { 5 | Item Get(string itemId); 6 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Services/INotifier.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Services; 2 | 3 | public interface INotifier 4 | { 5 | void Notify(string itemId); 6 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Models/UpdateItemInCart.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Models; 2 | 3 | public class UpdateItemInCart 4 | { 5 | public int Quantity { get; set; } 6 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Features/Carts/UpdateItemInCartDto.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Models; 2 | 3 | public class UpdateItemInCartDto 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Models/UpdateItemInCart.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Models; 2 | 3 | public record UpdateItemInCart 4 | { 5 | public int Quantity { get; set; } 6 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Data/IItemRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Data; 2 | 3 | public interface IItemRepository 4 | { 5 | Item Get(string itemId); 6 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Services/INotifier.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Services; 2 | 3 | public interface INotifier 4 | { 5 | void Notify(string itemId); 6 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/IItemRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | public interface IItemRepository 4 | { 5 | Item? Get(ItemId itemId); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/IItemRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | 3 | public interface IItemRepository 4 | { 5 | Item? Get(ItemId itemId); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services.AddControllers(); 4 | 5 | var app = builder.Build(); 6 | 7 | app.MapControllers(); 8 | 9 | app.Run(); -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services.AddControllers(); 4 | 5 | var app = builder.Build(); 6 | 7 | app.MapControllers(); 8 | 9 | app.Run(); -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Data/CartItem.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Data; 2 | 3 | public class CartItem 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Data/CartItem.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Data; 2 | 3 | public class CartItem 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Data/Cart.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Data; 2 | 3 | public class Cart 4 | { 5 | public string Id { get; set; } 6 | 7 | public IEnumerable Items { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Handlers/IRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Handlers; 2 | 3 | public interface IRequestHandler where TIn : IRequest 4 | { 5 | TOut Handle(TIn input); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Data/Cart.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Data; 2 | 3 | public class Cart 4 | { 5 | public string Id { get; set; } 6 | 7 | public IEnumerable Items { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/ItemSaleRule/IItemSaleRule.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain.ItemSaleRule; 2 | 3 | public interface IItemSaleRule 4 | { 5 | void Validate(Cart cart, Item item, int quantity); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/OOPsIDidItAgain.04.OOifying.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OOPsIDidItAgain._04.OOifying.Web 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Models/CartItemModel.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Models; 2 | 3 | public class CartItemModel 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Models/CartModel.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Models; 2 | 3 | public class CartModel 4 | { 5 | public string Id { get; set; } 6 | 7 | public IEnumerable Items { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Models/AddItemToCartModel.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Models; 2 | 3 | public class AddItemToCartModel 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Features/Carts/CartItemDto.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Features.Carts; 2 | 3 | public class CartItemDto 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._04.OOifying.Web.Features.Carts; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | var app = builder.Build(); 6 | 7 | AddItemToCartEndpoint.MapEndpoint(app); 8 | 9 | app.Run(); -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Handlers/IRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Handlers; 2 | 3 | public interface IRequestHandler where TIn : IRequest 4 | { 5 | TOut Handle(TIn input); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Shared/IStronglyTypedId.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 2 | 3 | public interface IStronglyTypedId 4 | { 5 | Guid Value { get; } // to simplify the demo, hardcoded to Guid 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Handlers/IRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Handlers; 2 | 3 | public interface IRequestHandler where TIn : IRequest 4 | { 5 | TOut Handle(TIn input); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Models/AddItemToCartModel.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Models; 2 | 3 | public class AddItemToCartModel 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Models/CartModel.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Models; 2 | 3 | public record CartModel 4 | { 5 | public string Id { get; set; } 6 | 7 | public IEnumerable Items { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/OOPsIDidItAgain.02.SuperService.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OOPsIDidItAgain._02.SuperService.Web 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Data/CartItem.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Data; 2 | 3 | public class CartItem 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Features/Carts/CartDto.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Features.Carts; 2 | 3 | public class CartDto 4 | { 5 | public string Id { get; set; } 6 | 7 | public IEnumerable Items { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/ItemSaleRule/IItemSaleRule.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.ItemSaleRule; 2 | 3 | public interface IItemSaleRule 4 | { 5 | void Validate(Cart cart, Item item, int quantity); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Data/Cart.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Data; 2 | 3 | public class Cart 4 | { 5 | public string Id { get; set; } 6 | 7 | public IEnumerable Items { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Handlers/IRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Handlers; 2 | 3 | public interface IRequestHandler where TIn : IRequest 4 | { 5 | TOut Handle(TIn input); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Features/Carts/AddItemToCartDto.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Features.Carts; 2 | 3 | public class AddItemToCartDto 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Features/Carts/CartItemDto.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Models; 4 | 5 | public record CartItemDto(ItemId ItemId, int Quantity); 6 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/OOPsIDidItAgain.01.SuperController.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OOPsIDidItAgain._01.SuperController.Web 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Models/CartItemModel.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Models 2 | { 3 | public class CartItemModel 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/ICartRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain; 2 | 3 | public interface ICartRepository 4 | { 5 | Cart Get(string id); 6 | 7 | Cart Save(Cart cart); 8 | 9 | void Delete(string id); 10 | } 11 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/Item.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain; 2 | 3 | public class Item 4 | { 5 | public Item() 6 | { 7 | Id = Guid.NewGuid().ToString(); 8 | } 9 | 10 | public string Id { get; } 11 | } 12 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Features/Carts/CartDto.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Models; 4 | 5 | public record CartDto(CartId Id, IEnumerable Items); 6 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Data/ICartRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Data; 2 | 3 | public interface ICartRepository 4 | { 5 | Cart Get(string id); 6 | 7 | Cart Save(Cart cart); 8 | 9 | void Delete(string id); 10 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Services/INotifier.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Services; 4 | 5 | public interface INotifier 6 | { 7 | void Notify(ItemId itemId); 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Features/Carts/CartItemDto.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Features.Carts; 4 | 5 | public record CartItemDto(ItemId ItemId, int Quantity); 6 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Shared/Unit.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | public sealed class Unit 4 | { 5 | private Unit() 6 | { 7 | } 8 | 9 | public static Unit Instance { get; } = new(); 10 | } 11 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Features/Carts/CartItemDto.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Features.Carts; 2 | 3 | public class CartItemDto 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Exceptions/NotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Exceptions; 2 | 3 | public class NotFoundException : Exception 4 | { 5 | public NotFoundException(string message) : base(message) 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Features/Carts/AddItemToCartDto.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Features.Carts; 4 | 5 | public record AddItemToCartDto(ItemId ItemId, int Quantity); 6 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Features/Carts/CartDto.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Features.Carts; 4 | 5 | public record CartDto(CartId Id, IEnumerable Items); 6 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Services/INotifier.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Services; 4 | 5 | public interface INotifier 6 | { 7 | void Notify(ItemId itemId); 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Data/ICartRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Data; 2 | 3 | public interface ICartRepository 4 | { 5 | Cart Get(string id); 6 | 7 | Cart Save(Cart cart); 8 | 9 | void Delete(string id); 10 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Exceptions/ValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Exceptions; 2 | 3 | public class ValidationException : Exception 4 | { 5 | public ValidationException(string message) : base(message) 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Shared/Unit.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 2 | 3 | public sealed class Unit 4 | { 5 | private Unit() 6 | { 7 | 8 | } 9 | 10 | public static Unit Instance { get; } = new Unit(); 11 | } 12 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Features/Carts/AddItemToCartDto.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Features.Carts; 4 | 5 | public record AddItemToCartDto(ItemId ItemId, int Quantity); 6 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Features/Carts/AddItemToCartDto.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Features.Carts; 2 | 3 | public class AddItemToCartDto 4 | { 5 | public string ItemId { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/IItemSaleRuleRepository.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._04.OOifying.Web.Domain.ItemSaleRule; 2 | 3 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain; 4 | 5 | public interface IItemSaleRuleRepository 6 | { 7 | IItemSaleRule GetForItem(string itemId); 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/OOPsIDidItAgain.03.IndividualRequestHandlers.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OOPsIDidItAgain._03.IndividualRequestHandlers.Web 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/PostAddItemToCartListeners/IPostAddItemToCartListener.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain.PostAddItemToCartListeners; 2 | 3 | public interface IPostAddItemToCartListener 4 | { 5 | void OnAdded(Cart cart, Item item, CartItem cartItem); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Exceptions/NotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Exceptions; 2 | 3 | public class NotFoundException : Exception 4 | { 5 | public NotFoundException(string message) : base(message) 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/ItemSaleRule/NoopItemSaleRule.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain.ItemSaleRule; 2 | 3 | public class NoopItemSaleRule : IItemSaleRule 4 | { 5 | public void Validate(Cart cart, Item item, int quantity) 6 | { 7 | // NOOP 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Exceptions/ValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Exceptions; 2 | 3 | public class ValidationException : Exception 4 | { 5 | public ValidationException(string message) : base(message) 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/PostAddItemToCartListeners/IPostAddItemToCartListener.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.PostAddItemToCartListeners; 2 | 3 | public interface IPostAddItemToCartListener 4 | { 5 | void OnAdded(Cart cart, Item item, CartItem cartItem); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/PostAddItemToCartListeners/IPostAddItemToCartListener.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.PostAddItemToCartListeners; 2 | 3 | public interface IPostAddItemToCartListener 4 | { 5 | void OnAdded(Cart cart, Item item, CartItem cartItem); 6 | } 7 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Exceptions/NotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Exceptions 2 | { 3 | public class NotFoundException: Exception 4 | { 5 | public NotFoundException(string message) : base(message) 6 | { 7 | 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/IItemSaleRuleRepository.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.ItemSaleRule; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 4 | 5 | public interface IItemSaleRuleRepository 6 | { 7 | IItemSaleRule GetForItem(ItemId itemId); 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information", 7 | "OOPsIDidItAgain": "Trace" 8 | } 9 | }, 10 | "AllowedHosts": "*" 11 | } 12 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Data/ICartRepository.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Data 2 | { 3 | public interface ICartRepository 4 | { 5 | Cart Get(string id); 6 | 7 | Cart Save(Cart cart); 8 | 9 | void Delete(string id); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/IItemSaleRuleRepository.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.ItemSaleRule; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 4 | 5 | public interface IItemSaleRuleRepository 6 | { 7 | IItemSaleRule GetForItem(ItemId itemId); 8 | } 9 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Exceptions/ValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Exceptions 2 | { 3 | public class ValidationException : Exception 4 | { 5 | public ValidationException(string message) : base(message) 6 | { 7 | 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/ItemSaleRule/NoopItemSaleRule.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.ItemSaleRule; 2 | 3 | public class NoopItemSaleRule : IItemSaleRule 4 | { 5 | public void Validate(Cart cart, Item item, int quantity) 6 | { 7 | // NOOP 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/ItemSaleRule/IItemSaleRule.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.ItemSaleRule; 4 | 5 | public interface IItemSaleRule 6 | { 7 | Either Validate(Cart cart, Item item, int quantity); 8 | } 9 | -------------------------------------------------------------------------------- /sample-requests.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:5000/api/carts/b9ce4fbf-dd5a-45aa-8a03-0f7fd5ad9f6a/items 2 | Content-Type: application/json 3 | Accept: application/json 4 | 5 | { 6 | "itemId": "987-6543210", 7 | "quantity": 1 8 | } 9 | 10 | ### 11 | 12 | GET http://localhost:5000/api/carts/b9ce4fbf-dd5a-45aa-8a03-0f7fd5ad9f6a 13 | Accept: application/json -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Data/Item.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._02.SuperService.Web.Data; 2 | 3 | public class Item 4 | { 5 | public string Id { get; set; } 6 | 7 | public int? MaximumQuantity { get; set; } 8 | 9 | public TimeSpan? MinimumTimeOfDayToSell { get; set; } 10 | 11 | public bool IsInWatchlist { get; set; } 12 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Data/Item.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._01.SuperController.Web.Data; 2 | 3 | public class Item 4 | { 5 | public string Id { get; set; } 6 | 7 | public int? MaximumQuantity { get; set; } 8 | 9 | public TimeSpan? MinimumTimeOfDayToSell { get; set; } 10 | 11 | public bool IsInWatchlist { get; set; } 12 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/Item.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | public class Item 4 | { 5 | private Item(ItemId id) 6 | { 7 | Id = id; 8 | } 9 | 10 | public ItemId Id { get; } 11 | 12 | public static Item From(ItemId itemId) 13 | => new (itemId); 14 | } 15 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/Item.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | 3 | public class Item 4 | { 5 | private Item(ItemId id) 6 | { 7 | Id = id; 8 | } 9 | 10 | public ItemId Id { get; } 11 | 12 | public static Item From(ItemId itemId) 13 | => new (itemId); 14 | } 15 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/DomainException.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | public class DomainException : Exception 4 | { 5 | public DomainException(ErrorDetail errorDetail) 6 | { 7 | ErrorDetail = errorDetail; 8 | } 9 | 10 | public ErrorDetail ErrorDetail { get; } 11 | } 12 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/ICartRepository.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 4 | 5 | public interface ICartRepository 6 | { 7 | Maybe Get(CartId id); 8 | 9 | Cart Save(Cart cart); 10 | 11 | void Delete(CartId id); 12 | } 13 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/ICartRepository.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 4 | 5 | public interface ICartRepository 6 | { 7 | Maybe Get(CartId id); 8 | 9 | Cart Save(Cart cart); 10 | 11 | void Delete(CartId id); 12 | } 13 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Shared/IStronglyTypedId.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 4 | 5 | public interface IStronglyTypedId 6 | { 7 | static abstract bool TryParse(string? input, [MaybeNullWhen(false)] out TId result); 8 | 9 | string AsString(); 10 | } 11 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web 5 | enable 6 | nullable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Data/Item.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Data; 2 | 3 | public class Item 4 | { 5 | public string Id { get; set; } 6 | 7 | public int? MaximumQuantity { get; set; } 8 | 9 | public TimeSpan? MinimumTimeOfDayToSell { get; set; } 10 | 11 | public bool IsInWatchlist { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Features/ResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Features; 4 | 5 | public static class ResultExtensions 6 | { 7 | public static IResult ToResult(this TResult result) => Results.Ok(result); 8 | 9 | public static IResult ToResult(this Unit result) => Results.NoContent(); 10 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Features.Carts; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | builder.Services.AddControllers(); 6 | 7 | var app = builder.Build(); 8 | 9 | // use controllers 10 | app.MapControllers(); 11 | 12 | // or use endpoints 13 | AddItemToCartEndpoint.MapEndpoint(app); 14 | 15 | // (or both, they're not mutually exclusive) 16 | 17 | app.Run(); -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Features.Carts; 2 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Middlewares; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | builder.Services.AddSingleton(); 7 | 8 | var app = builder.Build(); 9 | 10 | app.UseMiddleware(); 11 | 12 | AddItemToCartEndpoint.MapEndpoint(app); 13 | 14 | app.Run(); -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/OOPsIDidItAgain.06.MinimizingExceptions.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OOPsIDidItAgain._06.MinimizingExceptions.Web 5 | enable 6 | nullable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/ItemSaleRule/NoopItemSaleRule.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.ItemSaleRule; 4 | 5 | public class NoopItemSaleRule : IItemSaleRule 6 | { 7 | private static readonly Either NoopResult = Either.Right(Unit.Instance); 8 | 9 | public Either Validate(Cart cart, Item item, int quantity) => NoopResult; 10 | } 11 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | true 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Services/ICartsService.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._02.SuperService.Web.Data; 2 | 3 | namespace OOPsIDidItAgain._02.SuperService.Web.Services; 4 | 5 | public interface ICartsService 6 | { 7 | Cart Get(string cartId); 8 | 9 | Cart CreateCart(); 10 | 11 | void AddItemToCart(string cartId, string itemId, int quantity); 12 | 13 | void UpdateItemInCart(string cartId, string itemId, int quantity); 14 | 15 | void RemoveItemFromCart(string cartId, string itemId); 16 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/CartId.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | public readonly record struct CartId(Guid Value) 4 | { 5 | public static bool TryParse(string? input, out CartId result) 6 | { 7 | if (Guid.TryParse(input, out var value)) 8 | { 9 | result = new(value); 10 | return true; 11 | } 12 | 13 | result = default; 14 | return false; 15 | } 16 | 17 | public static CartId New() => new(Guid.NewGuid()); 18 | } 19 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Infrastructure/LoggingNotifier.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Services; 3 | 4 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Infrastructure; 5 | 6 | public class LoggingNotifier : INotifier 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public LoggingNotifier(ILogger logger) => _logger = logger; 11 | 12 | public void Notify(ItemId itemId) => _logger.LogDebug(itemId.ToString()); 13 | } 14 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Shared/MaybeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | public static class MaybeExtensions 4 | { 5 | public static TOut MapValueOr(this Maybe maybeValue, Func some, Func none) 6 | { 7 | if (some is null) throw new ArgumentNullException(nameof(some)); 8 | if (none is null) throw new ArgumentNullException(nameof(none)); 9 | 10 | return maybeValue.TryGetValue(out var value) 11 | ? some(value) 12 | : none(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Middlewares/ResultMappingErrorVisitor.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Middlewares; 4 | 5 | public class ResultMappingErrorVisitor : ErrorDetail.IResultVisitor 6 | { 7 | public IResult Visit(ErrorDetail.NotFound result) 8 | => Results.NotFound(result.Message); 9 | 10 | public IResult Visit(ErrorDetail.Invalid result) 11 | => Results.BadRequest(result.Message); 12 | 13 | // public IResult Visit(ErrorDetail.Unexpected result) 14 | // => Results.StatusCode(500); 15 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/CartItem.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain; 2 | 3 | public class CartItem 4 | { 5 | public CartItem(string itemId, int quantity) 6 | { 7 | ItemId = itemId != default 8 | ? itemId 9 | : throw new ArgumentException($"{nameof(itemId)} cannot be the default value.", nameof(itemId)); 10 | Quantity = quantity > 0 11 | ? quantity 12 | : throw new ArgumentException($"{nameof(quantity)} must be greater than 0..", nameof(quantity)); 13 | } 14 | 15 | public string ItemId { get; } 16 | 17 | public int Quantity { get; } 18 | } 19 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/ItemSaleRule/CompositeItemSaleRule.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain.ItemSaleRule; 2 | 3 | public class CompositeItemSaleRule : IItemSaleRule 4 | { 5 | private readonly IEnumerable _rules; 6 | 7 | public CompositeItemSaleRule(IEnumerable rules) 8 | { 9 | _rules = rules ?? throw new ArgumentNullException(nameof(rules)); 10 | } 11 | 12 | public void Validate(Cart cart, Item item, int quantity) 13 | { 14 | foreach (var rule in _rules) 15 | { 16 | rule.Validate(cart, item, quantity); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/CartItem.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 4 | 5 | public class CartItem 6 | { 7 | public CartItem(ItemId itemId, int quantity) 8 | { 9 | Require.NotDefault(itemId, nameof(itemId)); 10 | 11 | Quantity = quantity > 0 12 | ? quantity 13 | : throw new ArgumentException($"{nameof(quantity)} must be greater than 0.", nameof(quantity)); 14 | 15 | ItemId = itemId; 16 | } 17 | 18 | public ItemId ItemId { get; } 19 | 20 | public int Quantity { get; } 21 | } 22 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/CartItem.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 4 | 5 | public class CartItem 6 | { 7 | public CartItem(ItemId itemId, int quantity) 8 | { 9 | Require.NotDefault(itemId, nameof(itemId)); 10 | 11 | Quantity = quantity > 0 12 | ? quantity 13 | : throw new ArgumentException($"{nameof(quantity)} must be greater than 0.", nameof(quantity)); 14 | 15 | ItemId = itemId; 16 | } 17 | 18 | public ItemId ItemId { get; } 19 | 20 | public int Quantity { get; } 21 | } 22 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/ItemSaleRule/CompositeItemSaleRule.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.ItemSaleRule; 2 | 3 | public class CompositeItemSaleRule : IItemSaleRule 4 | { 5 | private readonly IEnumerable _rules; 6 | 7 | public CompositeItemSaleRule(IEnumerable rules) 8 | { 9 | _rules = rules ?? throw new ArgumentNullException(nameof(rules)); 10 | } 11 | 12 | public void Validate(Cart cart, Item item, int quantity) 13 | { 14 | foreach (var rule in _rules) 15 | { 16 | rule.Validate(cart, item, quantity); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/ItemSaleRule/MaximumQuantityPerSaleRule.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.ItemSaleRule; 2 | 3 | public class MaximumQuantityPerSaleRule : IItemSaleRule 4 | { 5 | public MaximumQuantityPerSaleRule(int maximumQuantityPerSale) 6 | { 7 | MaximumQuantityPerSale = maximumQuantityPerSale; 8 | } 9 | 10 | public int MaximumQuantityPerSale { get; } 11 | 12 | public void Validate(Cart cart, Item item, int quantity) 13 | { 14 | if (MaximumQuantityPerSale < quantity) 15 | { 16 | throw new DomainException(new ErrorDetail.Invalid("Quantity not allowed")); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Infrastructure/InMemoryItemRepository.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 3 | 4 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Infrastructure; 5 | 6 | public class InMemoryItemRepository : IItemRepository 7 | { 8 | private readonly List _items = new() 9 | { 10 | Item.From(((Either.Right) ItemId.From("987", "6543210")).Value), 11 | Item.From(((Either.Right) ItemId.From("123", "4567890")).Value) 12 | }; 13 | 14 | public Item? Get(ItemId itemId) 15 | => _items.FirstOrDefault(i => i.Id.Equals(itemId)); 16 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/ItemSaleRule/MaximumQuantityPerSaleRule.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._04.OOifying.Web.Exceptions; 2 | 3 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain.ItemSaleRule; 4 | 5 | public class MaximumQuantityPerSaleRule : IItemSaleRule 6 | { 7 | public MaximumQuantityPerSaleRule(int maximumQuantityPerSale) 8 | { 9 | MaximumQuantityPerSale = maximumQuantityPerSale; 10 | } 11 | 12 | public int MaximumQuantityPerSale { get; } 13 | 14 | public void Validate(Cart cart, Item item, int quantity) 15 | { 16 | if (MaximumQuantityPerSale < quantity) 17 | { 18 | throw new ValidationException("Quantity not allowed"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/PostAddItemToCartListeners/CompositePostAddItemToCartListener.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain.PostAddItemToCartListeners; 2 | 3 | public class CompositePostAddItemToCartListener : IPostAddItemToCartListener 4 | { 5 | private readonly IReadOnlyCollection _listeners; 6 | 7 | public CompositePostAddItemToCartListener(IReadOnlyCollection listeners) 8 | => _listeners = listeners; 9 | 10 | public void OnAdded(Cart cart, Item item, CartItem cartItem) 11 | { 12 | foreach (var listener in _listeners) 13 | { 14 | listener.OnAdded(cart, item, cartItem); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/ItemSaleRule/MinimumTimeOfDayForSaleRule.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.ItemSaleRule; 2 | 3 | public class MinimumTimeOfDayForSaleRule : IItemSaleRule 4 | { 5 | public MinimumTimeOfDayForSaleRule(TimeSpan minimumTimeOfDayForSale) 6 | { 7 | MinimumTimeOfDayForSale = minimumTimeOfDayForSale; 8 | } 9 | 10 | public TimeSpan MinimumTimeOfDayForSale { get; } 11 | 12 | public void Validate(Cart cart, Item item, int quantity) 13 | { 14 | if (MinimumTimeOfDayForSale > DateTime.Now.TimeOfDay) 15 | { 16 | throw new DomainException(new ErrorDetail.Invalid("Can't buy that yet!")); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/ItemSaleRule/MinimumTimeOfDayForSaleRule.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._04.OOifying.Web.Exceptions; 2 | 3 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain.ItemSaleRule; 4 | 5 | public class MinimumTimeOfDayForSaleRule : IItemSaleRule 6 | { 7 | public MinimumTimeOfDayForSaleRule(TimeSpan minimumTimeOfDayForSale) 8 | { 9 | MinimumTimeOfDayForSale = minimumTimeOfDayForSale; 10 | } 11 | 12 | public TimeSpan MinimumTimeOfDayForSale { get; } 13 | 14 | public void Validate(Cart cart, Item item, int quantity) 15 | { 16 | if (MinimumTimeOfDayForSale > DateTime.Now.TimeOfDay) 17 | { 18 | throw new ValidationException("Can't buy that yet!"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/PostAddItemToCartListeners/CompositePostAddItemToCartListener.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.PostAddItemToCartListeners; 2 | 3 | public class CompositePostAddItemToCartListener : IPostAddItemToCartListener 4 | { 5 | private readonly IReadOnlyCollection _listeners; 6 | 7 | public CompositePostAddItemToCartListener(IReadOnlyCollection listeners) 8 | { 9 | _listeners = listeners; 10 | } 11 | 12 | public void OnAdded(Cart cart, Item item, CartItem cartItem) 13 | { 14 | foreach (var listener in _listeners) 15 | { 16 | listener.OnAdded(cart, item, cartItem); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/PostAddItemToCartListeners/CompositePostAddItemToCartListener.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.PostAddItemToCartListeners; 2 | 3 | public class CompositePostAddItemToCartListener : IPostAddItemToCartListener 4 | { 5 | private readonly IReadOnlyCollection _listeners; 6 | 7 | public CompositePostAddItemToCartListener(IReadOnlyCollection listeners) 8 | { 9 | _listeners = listeners; 10 | } 11 | 12 | public void OnAdded(Cart cart, Item item, CartItem cartItem) 13 | { 14 | foreach (var listener in _listeners) 15 | { 16 | listener.OnAdded(cart, item, cartItem); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Infrastructure/InMemoryItemSaleRuleRepository.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.ItemSaleRule; 3 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 4 | 5 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Infrastructure; 6 | 7 | public class InMemoryItemSaleRuleRepository : IItemSaleRuleRepository 8 | { 9 | public IItemSaleRule GetForItem(ItemId itemId) 10 | { 11 | // cheating with the cast 😛 12 | var value = ((Either.Right) ItemId.From("987", "6543210")).Value; 13 | 14 | return itemId.Equals(value) 15 | ? new MaximumQuantityPerSaleRule(2) 16 | : new NoopItemSaleRule(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/ItemSaleRule/MaximumQuantityPerSaleRule.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.ItemSaleRule; 4 | 5 | public class MaximumQuantityPerSaleRule : IItemSaleRule 6 | { 7 | public MaximumQuantityPerSaleRule(int maximumQuantityPerSale) 8 | { 9 | MaximumQuantityPerSale = maximumQuantityPerSale; 10 | } 11 | 12 | public int MaximumQuantityPerSale { get; } 13 | 14 | public Either Validate(Cart cart, Item item, int quantity) 15 | => quantity > MaximumQuantityPerSale 16 | ? Either.Left(new Error.Invalid("Quantity not allowed")) 17 | : Either.Right(Unit.Instance); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/ItemSaleRule/MinimumTimeOfDayForSaleRule.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.ItemSaleRule; 4 | 5 | public class MinimumTimeOfDayForSaleRule : IItemSaleRule 6 | { 7 | public MinimumTimeOfDayForSaleRule(TimeSpan minimumTimeOfDayForSale) 8 | { 9 | MinimumTimeOfDayForSale = minimumTimeOfDayForSale; 10 | } 11 | 12 | public TimeSpan MinimumTimeOfDayForSale { get; } 13 | 14 | public Either Validate(Cart cart, Item item, int quantity) 15 | => MinimumTimeOfDayForSale > DateTime.Now.TimeOfDay 16 | ? Either.Left(new Error.Invalid("Can't buy that yet!")) 17 | : Either.Right(Unit.Instance); 18 | } 19 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Features/Carts/AddItemToCartEndpoint.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Handlers; 3 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 4 | 5 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Features.Carts; 6 | 7 | public static class AddItemToCartEndpoint 8 | { 9 | public static void MapEndpoint(WebApplication app) 10 | => app.MapPost( 11 | "api/carts/{cartId}/items", 12 | (CartId cartId, 13 | AddItemToCartDto addItemToCart, 14 | IRequestHandler handler) 15 | => handler 16 | .Handle(new AddItemToCart.Request(cartId, addItemToCart.ItemId, addItemToCart.Quantity)) 17 | .ToResult()); 18 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/PostAddItemToCartListeners/WatchlistNotifierListener.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._04.OOifying.Web.Services; 2 | 3 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain.PostAddItemToCartListeners; 4 | 5 | public class WatchlistNotifierListener : IPostAddItemToCartListener 6 | { 7 | private readonly INotifier _notifier; 8 | private readonly HashSet _itemsInWatchlist; 9 | 10 | public WatchlistNotifierListener(INotifier notifier, IReadOnlyCollection itemsInWatchlist) 11 | { 12 | _notifier = notifier; 13 | _itemsInWatchlist = new HashSet(itemsInWatchlist); 14 | } 15 | 16 | public void OnAdded(Cart cart, Item item, CartItem cartItem) 17 | { 18 | if (_itemsInWatchlist.Contains(item.Id)) 19 | { 20 | _notifier.Notify(item.Id); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Shared/Require.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 4 | 5 | public static class Require 6 | { 7 | public static void NotNull( 8 | T input, 9 | [CallerArgumentExpression("input")] string? parameterName = null) 10 | where T : class 11 | { 12 | if (input is null) 13 | { 14 | throw new ArgumentNullException(parameterName); 15 | } 16 | } 17 | 18 | public static void NotDefault( 19 | T input, 20 | [CallerArgumentExpression("input")] string? parameterName = null) 21 | where T : IEquatable 22 | { 23 | if (input.Equals(default)) 24 | { 25 | throw new ArgumentException("Unexpected default value", parameterName); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/CartId.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.ModelBinding; 3 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 4 | 5 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 6 | 7 | [TypeConverter(typeof(StronglyTypedIdTypeConverter))] 8 | public readonly record struct CartId(Guid Value) : IStronglyTypedId 9 | { 10 | public string AsString() => Value.ToString(); 11 | 12 | public static bool TryParse(string? input, out CartId result) 13 | { 14 | if (Guid.TryParse(input, out var value)) 15 | { 16 | result = new(value); 17 | return true; 18 | } 19 | 20 | result = default; 21 | return false; 22 | } 23 | 24 | public static CartId New() => new(Guid.NewGuid()); 25 | } 26 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Shared/Require.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 4 | 5 | public static class Require 6 | { 7 | public static void NotNull( 8 | T input, 9 | [CallerArgumentExpression("input")] string? parameterName = null) 10 | where T : class 11 | { 12 | if (input is null) 13 | { 14 | throw new ArgumentNullException(parameterName); 15 | } 16 | } 17 | 18 | public static void NotDefault( 19 | T input, 20 | [CallerArgumentExpression("input")] string? parameterName = null) 21 | where T : IEquatable 22 | { 23 | if (input.Equals(default)) 24 | { 25 | throw new ArgumentException("Unexpected default value", parameterName); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Middlewares/ExceptionHandlingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Middlewares; 4 | 5 | public class ExceptionHandlingMiddleware : IMiddleware 6 | { 7 | private readonly ResultMappingErrorVisitor _resultMappingErrorVisitor; 8 | 9 | public ExceptionHandlingMiddleware() => _resultMappingErrorVisitor = new ResultMappingErrorVisitor(); 10 | 11 | public async Task InvokeAsync(HttpContext context, RequestDelegate next) 12 | { 13 | try 14 | { 15 | await next(context); 16 | } 17 | catch (DomainException domainException) 18 | { 19 | var result = domainException.ErrorDetail.Accept(_resultMappingErrorVisitor); 20 | await result.ExecuteAsync(context); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Features/Carts/AddItemToCartEndpoint.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Handlers; 3 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 4 | 5 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Features.Carts; 6 | 7 | public static class AddItemToCartEndpoint 8 | { 9 | public static void MapEndpoint(WebApplication app) 10 | => app.MapPost( 11 | "api/carts/{cartId}/items", 12 | (CartId cartId, 13 | AddItemToCartDto addItemToCart, 14 | IRequestHandler> handler) 15 | => handler 16 | .Handle(new AddItemToCart.Request(cartId, addItemToCart.ItemId, addItemToCart.Quantity)) 17 | .ToResult()); 18 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/PostAddItemToCartListeners/WatchlistNotifierListener.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Services; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.PostAddItemToCartListeners; 4 | 5 | public class WatchlistNotifierListener : IPostAddItemToCartListener 6 | { 7 | private readonly INotifier _notifier; 8 | private readonly HashSet _itemsInWatchlist; 9 | 10 | public WatchlistNotifierListener(INotifier notifier, IReadOnlyCollection itemsInWatchlist) 11 | { 12 | _notifier = notifier; 13 | _itemsInWatchlist = new HashSet(itemsInWatchlist); 14 | } 15 | 16 | public void OnAdded(Cart cart, Item item, CartItem cartItem) 17 | { 18 | if (_itemsInWatchlist.Contains(item.Id)) 19 | { 20 | _notifier.Notify(item.Id); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/PostAddItemToCartListeners/WatchlistNotifierListener.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Services; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.PostAddItemToCartListeners; 4 | 5 | public class WatchlistNotifierListener : IPostAddItemToCartListener 6 | { 7 | private readonly INotifier _notifier; 8 | private readonly HashSet _itemsInWatchlist; 9 | 10 | public WatchlistNotifierListener(INotifier notifier, IReadOnlyCollection itemsInWatchlist) 11 | { 12 | _notifier = notifier; 13 | _itemsInWatchlist = new HashSet(itemsInWatchlist); 14 | } 15 | 16 | public void OnAdded(Cart cart, Item item, CartItem cartItem) 17 | { 18 | if (_itemsInWatchlist.Contains(item.Id)) 19 | { 20 | _notifier.Notify(item.Id); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/ItemId.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 4 | 5 | public readonly record struct ItemId 6 | { 7 | private static readonly Regex Regex = new("^(\\d{3})-(\\d{7})$", RegexOptions.Compiled); 8 | 9 | private ItemId(string prefix, string suffix) 10 | { 11 | Prefix = prefix; 12 | Suffix = suffix; 13 | } 14 | 15 | public string Prefix { get; } 16 | 17 | public string Suffix { get; } 18 | 19 | public static bool TryParse(string? input, out ItemId result) 20 | { 21 | Match match; 22 | if (!string.IsNullOrWhiteSpace(input) && (match = Regex.Match(input)).Success) 23 | { 24 | result = new(match.Groups[1].Value, match.Groups[2].Value); 25 | return true; 26 | } 27 | 28 | result = default; 29 | return false; 30 | } 31 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/ItemSaleRule/CompositeItemSaleRule.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.ItemSaleRule; 4 | 5 | public class CompositeItemSaleRule : IItemSaleRule 6 | { 7 | private readonly IEnumerable _rules; 8 | 9 | public CompositeItemSaleRule(IEnumerable rules) 10 | { 11 | _rules = rules ?? throw new ArgumentNullException(nameof(rules)); 12 | } 13 | 14 | public Either Validate(Cart cart, Item item, int quantity) 15 | { 16 | foreach (var rule in _rules) 17 | { 18 | var result = rule.Validate(cart, item, quantity); 19 | if (result is not Either.Right) 20 | { 21 | return result; 22 | } 23 | } 24 | 25 | return Either.Right(Unit.Instance); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Handlers/RequestHandlerLoggingDecorator.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._04.OOifying.Web.Handlers; 2 | 3 | public class RequestHandlerLoggingDecorator : IRequestHandler where TIn : IRequest 4 | { 5 | private readonly IRequestHandler _next; 6 | private readonly ILogger> _logger; 7 | 8 | public RequestHandlerLoggingDecorator(IRequestHandler next, ILogger> logger) 9 | { 10 | _next = next; 11 | _logger = logger; 12 | } 13 | 14 | public TOut Handle(TIn input) 15 | { 16 | try 17 | { 18 | _logger.LogTrace("Handling {requestName}", typeof(TIn).Name); 19 | return _next.Handle(input); 20 | } 21 | finally 22 | { 23 | _logger.LogTrace("Done handling {requestName}", typeof(TIn).Name); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Features/Carts/GetCartEndpoint.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Handlers; 3 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 4 | 5 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Features.Carts; 6 | 7 | public static class GetCartEndpoint 8 | { 9 | public static void MapEndpoint(WebApplication app) 10 | => app.MapGet( 11 | "api/carts/{cartId}", 12 | (CartId cartId, 13 | IRequestHandler> handler) 14 | => handler 15 | .Handle(new GetCart.Request(cartId)) 16 | .ToResult(response => new CartDto 17 | ( 18 | response.CartId, 19 | response.Items.Select(i => new CartItemDto(i.ItemId, i.Quantity)) 20 | ))); 21 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Handlers/RequestHandlerLoggingDecorator.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Handlers; 2 | 3 | public class RequestHandlerLoggingDecorator : IRequestHandler where TIn : IRequest 4 | { 5 | private readonly IRequestHandler _next; 6 | private readonly ILogger> _logger; 7 | 8 | public RequestHandlerLoggingDecorator(IRequestHandler next, ILogger> logger) 9 | { 10 | _next = next; 11 | _logger = logger; 12 | } 13 | 14 | public TOut Handle(TIn input) 15 | { 16 | try 17 | { 18 | _logger.LogTrace("Handling {requestName}", typeof(TIn).Name); 19 | return _next.Handle(input); 20 | } 21 | finally 22 | { 23 | _logger.LogTrace("Done handling {requestName}", typeof(TIn).Name); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Handlers/RequestHandlerLoggingDecorator.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Handlers; 2 | 3 | public class RequestHandlerLoggingDecorator : IRequestHandler where TIn : IRequest 4 | { 5 | private readonly IRequestHandler _next; 6 | private readonly ILogger> _logger; 7 | 8 | public RequestHandlerLoggingDecorator(IRequestHandler next, ILogger> logger) 9 | { 10 | _next = next; 11 | _logger = logger; 12 | } 13 | 14 | public TOut Handle(TIn input) 15 | { 16 | try 17 | { 18 | _logger.LogTrace("Handling {requestName}", typeof(TIn).Name); 19 | return _next.Handle(input); 20 | } 21 | finally 22 | { 23 | _logger.LogTrace("Done handling {requestName}", typeof(TIn).Name); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Shared/Either.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | public static class Either 4 | { 5 | public static Either Left(TLeft value) 6 | => new Either.Left(value); 7 | 8 | public static Either Right(TRight value) 9 | => new Either.Right(value); 10 | } 11 | 12 | public abstract class Either 13 | { 14 | private Either() 15 | { 16 | } 17 | 18 | public sealed class Left : Either 19 | { 20 | public Left(TLeft value) 21 | { 22 | Value = value; 23 | } 24 | 25 | public TLeft Value { get; } 26 | } 27 | 28 | public sealed class Right : Either 29 | { 30 | public Right(TRight value) 31 | { 32 | Value = value; 33 | } 34 | 35 | public TRight Value { get; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Handlers/RequestHandlerLoggingDecorator.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Handlers; 2 | 3 | public class RequestHandlerLoggingDecorator : IRequestHandler where TIn : IRequest 4 | { 5 | private readonly IRequestHandler _next; 6 | private readonly ILogger> _logger; 7 | 8 | public RequestHandlerLoggingDecorator(IRequestHandler next, ILogger> logger) 9 | { 10 | _next = next; 11 | _logger = logger; 12 | } 13 | 14 | public TOut Handle(TIn input) 15 | { 16 | try 17 | { 18 | _logger.LogTrace("Handling {requestName}", typeof(TIn).Name); 19 | return _next.Handle(input); 20 | } 21 | finally 22 | { 23 | _logger.LogTrace("Done handling {requestName}", typeof(TIn).Name); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Infrastructure/InMemoryCartRepository.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 3 | 4 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Infrastructure; 5 | 6 | public class InMemoryCartRepository : ICartRepository 7 | { 8 | private readonly List _carts = new() 9 | { 10 | Cart.From(new CartId(Guid.Parse("b9ce4fbf-dd5a-45aa-8a03-0f7fd5ad9f6a")), Array.Empty()), 11 | Cart.From(new CartId(Guid.Parse("5562ece2-976f-4804-8e0a-0bdc4a7fe5c4")), Array.Empty()) 12 | }; 13 | 14 | public Maybe Get(CartId id) 15 | => Maybe.FromNullable(_carts.FirstOrDefault(c => c.Id == id)); 16 | 17 | public Cart Save(Cart cart) 18 | { 19 | // NOOP - we're doing stuff in memory for demo purposes 20 | 21 | return cart; 22 | } 23 | 24 | public void Delete(CartId id) => throw new NotImplementedException(); 25 | } 26 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Shared/Maybe.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 4 | 5 | // Simple implementation for demo 6 | // For a fully featured implementation, checkout https://github.com/nlkl/Optional or https://github.com/louthy/language-ext 7 | 8 | public static class Maybe 9 | { 10 | public static Maybe Some(T value) where T : notnull => new(value); 11 | 12 | public static Maybe None() where T : notnull => new(); 13 | } 14 | 15 | public readonly struct Maybe 16 | { 17 | private readonly T _value; 18 | 19 | internal Maybe(T value) 20 | { 21 | _value = value ?? throw new ArgumentNullException(nameof(value)); 22 | HasValue = true; 23 | } 24 | 25 | public bool HasValue { get; } 26 | 27 | public T ValueOr(Func alternativeFactory) => HasValue ? _value : alternativeFactory(); 28 | 29 | public bool TryGetValue([MaybeNullWhen(false)] out T value) 30 | { 31 | value = HasValue ? _value : default; 32 | return HasValue; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 João Antunes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Shared/Maybe.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 4 | 5 | // Simple implementation for demo 6 | // For a fully featured implementation, checkout https://github.com/nlkl/Optional or https://github.com/louthy/language-ext 7 | 8 | public static class Maybe 9 | { 10 | public static Maybe Some(T value) where T : notnull => new(value); 11 | 12 | public static Maybe None() where T : notnull => new(); 13 | 14 | public static Maybe FromNullable(T? value) where T : class => value is not null ? Some(value) : None(); 15 | } 16 | 17 | public readonly struct Maybe 18 | { 19 | private readonly T _value; 20 | 21 | internal Maybe(T value) 22 | { 23 | _value = value ?? throw new ArgumentNullException(nameof(value)); 24 | HasValue = true; 25 | } 26 | 27 | public bool HasValue { get; } 28 | 29 | public T ValueOr(Func alternativeFactory) => HasValue ? _value : alternativeFactory(); 30 | 31 | public bool TryGetValue([MaybeNullWhen(false)] out T value) 32 | { 33 | value = HasValue ? _value : default; 34 | return HasValue; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/ErrorDetail.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | 3 | public abstract record ErrorDetail 4 | { 5 | private ErrorDetail() 6 | { 7 | } 8 | 9 | public abstract TVisitResult Accept(IResultVisitor visitor); 10 | 11 | public interface IResultVisitor 12 | { 13 | TVisitResult Visit(NotFound result); 14 | 15 | TVisitResult Visit(Invalid result); 16 | 17 | //TVisitResult Visit(Unexpected result); 18 | } 19 | 20 | public sealed record NotFound(string Message) : ErrorDetail 21 | { 22 | public override TVisitResult Accept(IResultVisitor visitor) 23 | => visitor.Visit(this); 24 | } 25 | 26 | public sealed record Invalid(string Message) : ErrorDetail 27 | { 28 | public override TVisitResult Accept(IResultVisitor visitor) 29 | => visitor.Visit(this); 30 | } 31 | 32 | // public sealed record Unexpected(string Message) : ErrorDetail 33 | // { 34 | // public override TVisitResult Accept(IResultVisitor visitor) 35 | // => visitor.Visit(this); 36 | // } 37 | } 38 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Shared/Error.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | public abstract record Error 4 | { 5 | private Error() 6 | { 7 | } 8 | 9 | public abstract TResult Accept(TVisitor visitor) 10 | where TVisitor : IResultVisitor; 11 | 12 | public interface IResultVisitor 13 | { 14 | TVisitResult Visit(NotFound result); 15 | 16 | TVisitResult Visit(Invalid result); 17 | 18 | //TVisitResult Visit(Unexpected result); 19 | } 20 | 21 | public sealed record NotFound(string Message) : Error 22 | { 23 | public override TVisitResult Accept(TResultVisitor visitor) 24 | => visitor.Visit(this); 25 | } 26 | 27 | public sealed record Invalid(string Message) : Error 28 | { 29 | public override TVisitResult Accept(TResultVisitor visitor) 30 | => visitor.Visit(this); 31 | } 32 | 33 | // public sealed record Unexpected(string Message) : Error 34 | // { 35 | // public override TVisitResult Accept(TResultVisitor visitor) 36 | // => visitor.Visit(this); 37 | // } 38 | } 39 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Features/Carts/GetCart.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Handlers; 3 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 4 | 5 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Features.Carts; 6 | 7 | public static class GetCart 8 | { 9 | public record Request(CartId CartId) : IRequest>; 10 | 11 | public record Response(CartId CartId, IReadOnlyCollection Items) 12 | { 13 | public record CartItem(ItemId ItemId, int Quantity); 14 | } 15 | 16 | public class Handler : IRequestHandler> 17 | { 18 | private readonly ICartRepository _cartRepository; 19 | 20 | public Handler(ICartRepository cartRepository) => _cartRepository = cartRepository; 21 | 22 | public Maybe Handle(Request input) 23 | => _cartRepository.Get(input.CartId).TryGetValue(out var cart) 24 | ? Maybe.Some( 25 | new Response( 26 | cart.Id, 27 | cart.Items.Select(i => new Response.CartItem(i.ItemId, i.Quantity)).ToList() 28 | )) 29 | : Maybe.None(); 30 | } 31 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Features/Carts/AddItemToCartEndpoint.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._04.OOifying.Web.Exceptions; 2 | using OOPsIDidItAgain._04.OOifying.Web.Handlers; 3 | 4 | namespace OOPsIDidItAgain._04.OOifying.Web.Features.Carts; 5 | 6 | public static class AddItemToCartEndpoint 7 | { 8 | public static void MapEndpoint(WebApplication app) 9 | => app.MapPost( 10 | "api/carts/{cartId}/items", 11 | (string cartId, AddItemToCartDto addItemToCart, 12 | IRequestHandler handler) => 13 | { 14 | try 15 | { 16 | _ = handler.Handle( 17 | new AddItemToCart.Request 18 | ( 19 | cartId, 20 | addItemToCart.ItemId, 21 | addItemToCart.Quantity 22 | )); 23 | 24 | return Results.NoContent(); 25 | } 26 | catch (ValidationException vex) 27 | { 28 | return Results.BadRequest(vex.Message); 29 | } 30 | catch (NotFoundException nex) 31 | { 32 | return Results.NotFound(nex.Message); 33 | } 34 | }); 35 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Features/Carts/CartsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Exceptions; 3 | using OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Handlers; 4 | 5 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Features.Carts; 6 | 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class CartsController : ControllerBase 10 | { 11 | [HttpPost("{cartId}/items")] 12 | public IActionResult AddItemToCart( 13 | string cartId, 14 | AddItemToCartDto addItemToCart, 15 | [FromServices] IRequestHandler handler) 16 | { 17 | try 18 | { 19 | _ = handler.Handle( 20 | new AddItemToCart.Request 21 | { 22 | CartId = cartId, 23 | ItemId = addItemToCart.ItemId, 24 | Quantity = addItemToCart.Quantity 25 | }); 26 | 27 | return NoContent(); 28 | } 29 | catch (ValidationException vex) 30 | { 31 | return BadRequest(vex.Message); 32 | } 33 | catch (NotFoundException nex) 34 | { 35 | return NotFound(nex.Message); 36 | } 37 | } 38 | 39 | // ... 40 | // all the other endpoints 41 | } 42 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Domain/Cart.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._04.OOifying.Web.Exceptions; 2 | 3 | namespace OOPsIDidItAgain._04.OOifying.Web.Domain; 4 | 5 | public class Cart 6 | { 7 | private readonly Dictionary _items; 8 | 9 | public Cart() 10 | { 11 | Id = Guid.NewGuid().ToString(); 12 | _items = new Dictionary(); 13 | } 14 | 15 | public string Id { get; } 16 | 17 | public IReadOnlyCollection Items => _items.Values; 18 | 19 | public CartItem AddItemToCart(Item item, int quantity) 20 | { 21 | if (_items.ContainsKey(item.Id)) 22 | { 23 | throw new ValidationException($"Item {item.Id} already in the cart."); 24 | } 25 | 26 | var cartItem = new CartItem(item.Id, quantity); 27 | _items.Add(item.Id, cartItem); 28 | return cartItem; 29 | } 30 | 31 | public CartItem UpdateItemInCart(string itemId, int quantity) 32 | { 33 | if (!_items.ContainsKey(itemId)) 34 | { 35 | throw new ValidationException($"Item {itemId} not in the cart."); 36 | } 37 | 38 | var cartItem = new CartItem(itemId, quantity); 39 | _items[itemId] = cartItem; 40 | return cartItem; 41 | } 42 | 43 | public void RemoveItemFromCart(string itemId) 44 | { 45 | _items.Remove(itemId); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Features/Carts/AddItemToCartEndpoint.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Exceptions; 2 | using OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Handlers; 3 | 4 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Features.Carts; 5 | 6 | public static class AddItemToCartEndpoint 7 | { 8 | public static void MapEndpoint(WebApplication app) 9 | => app.MapPost( 10 | "api/carts/{cartId}/items", 11 | (string cartId, AddItemToCartDto addItemToCart, 12 | IRequestHandler handler) => 13 | { 14 | try 15 | { 16 | _ = handler.Handle( 17 | new AddItemToCart.Request 18 | { 19 | CartId = cartId, 20 | ItemId = addItemToCart.ItemId, 21 | Quantity = addItemToCart.Quantity 22 | }); 23 | 24 | return Results.NoContent(); 25 | } 26 | catch (ValidationException vex) 27 | { 28 | return Results.BadRequest(vex.Message); 29 | } 30 | catch (NotFoundException nex) 31 | { 32 | return Results.NotFound(nex.Message); 33 | } 34 | }); 35 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/ModelBinding/StronglyTypedIdJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Globalization; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 6 | 7 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.ModelBinding; 8 | 9 | public class StronglyTypedIdJsonConverter : JsonConverter where TId : IStronglyTypedId 10 | { 11 | public override bool CanConvert(Type typeToConvert) 12 | => typeof(IStronglyTypedId).IsAssignableFrom(typeToConvert); 13 | 14 | public override void Write(Utf8JsonWriter writer, TId value, JsonSerializerOptions options) 15 | => writer.WriteStringValue(value.AsString()); 16 | 17 | public override TId? Read( 18 | ref Utf8JsonReader reader, 19 | Type typeToConvert, 20 | JsonSerializerOptions options) 21 | => TId.TryParse(reader.GetString(), out var result) 22 | ? result 23 | : default; 24 | } 25 | 26 | public class StronglyTypedIdTypeConverter : TypeConverter where TId : IStronglyTypedId 27 | { 28 | public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) 29 | => sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); 30 | 31 | public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) 32 | { 33 | var stringValue = value as string; 34 | if (TId.TryParse(stringValue, out var result)) 35 | { 36 | return result; 37 | } 38 | 39 | return base.ConvertFrom(context, culture, value); 40 | } 41 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # OOPs, I did it again (aka Untangling the mess we're making out of OOP) 2 | 3 | This repository contains the demo code and slides for my presentation titled "OOPs, I did it again" (aka "Untangling the mess we're making out of OOP"). 4 | 5 | Feel free to use the issues to ask questions (or suggest improvements). For other ways of contact, you can check out my website at [https://antunes.dev](https://antunes.dev). 6 | 7 | ## Abstract 8 | 9 | Object oriented is one of the most broadly used programming paradigms. Problem is, most of the times, even if we use a primarily OO programming language, we're not really taking advantage of it or other useful paradigms and language features, ending up in a mostly procedural scenario. 10 | 11 | In this session, I’d like to share some ideas to improve our code, making it easier to understand and maintain, taking better advantage of our languages’ capabilities, mixing paradigms as appropriate. 12 | 13 | None of these ideas are new, but it seems we keep forgetting them and get back to the same old mess. 14 | 15 | ## About the demo code 16 | 17 | All the demo projects compile, but only the 6th project is configured enough to run and handle a request. 18 | 19 | The idea of having it running is just to be able to see the discussed request in action, particularly so that anyone interested can have some pointers on how to implement some of the infrastructural things needed to have everything working together (e.g. model binding or JSON converters). 20 | 21 | **NOTE:** it should go without saying that this code, particularly infrastructure bits, is not production ready. The goal was to have code that would be able to illustrate some ideas. There are more steps to take to get it to production levels. 22 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Domain/Cart.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 4 | 5 | public class Cart 6 | { 7 | private readonly Dictionary _items; 8 | 9 | private Cart(CartId cartId, IReadOnlyCollection cartItems) 10 | { 11 | Require.NotDefault(cartId, nameof(cartId)); 12 | Require.NotNull(cartItems, nameof(cartItems)); 13 | 14 | Id = cartId; 15 | _items = cartItems.ToDictionary(i => i.ItemId, i => i); 16 | } 17 | 18 | public static Cart New() 19 | => new Cart(CartId.New(), Array.Empty()); 20 | 21 | public static Cart From(CartId cartId, IReadOnlyCollection cartItems) 22 | => new Cart(cartId, cartItems); 23 | 24 | public CartId Id { get; } 25 | 26 | public IReadOnlyCollection Items => _items.Values; 27 | 28 | public CartItem AddItemToCart(Item item, int quantity) 29 | { 30 | if (_items.ContainsKey(item.Id)) 31 | { 32 | throw new DomainException(new ErrorDetail.Invalid($"Item {item.Id} already in the cart.")); 33 | } 34 | 35 | var cartItem = new CartItem(item.Id, quantity); 36 | _items.Add(item.Id, cartItem); 37 | return cartItem; 38 | } 39 | 40 | public CartItem UpdateItemInCart(ItemId itemId, int quantity) 41 | { 42 | if (!_items.ContainsKey(itemId)) 43 | { 44 | throw new DomainException(new ErrorDetail.Invalid($"Item {itemId} not in the cart.")); 45 | } 46 | 47 | var cartItem = new CartItem(itemId, quantity); 48 | _items[itemId] = cartItem; 49 | return cartItem; 50 | } 51 | 52 | public void RemoveItemFromCart(ItemId itemId) 53 | { 54 | _items.Remove(itemId); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/ItemId.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Text.RegularExpressions; 3 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.ModelBinding; 4 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 5 | 6 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 7 | 8 | [TypeConverter(typeof(StronglyTypedIdTypeConverter))] 9 | public readonly record struct ItemId : IStronglyTypedId 10 | { 11 | private static readonly Regex Regex = new("^(\\d{3})-(\\d{7})$", RegexOptions.Compiled); 12 | 13 | private ItemId(string prefix, string suffix) 14 | { 15 | Prefix = prefix; 16 | Suffix = suffix; 17 | } 18 | 19 | public string Prefix { get; } 20 | 21 | public string Suffix { get; } 22 | 23 | public string AsString() => $"{Prefix}-{Suffix}"; 24 | 25 | public static bool TryParse(string? input, out ItemId result) 26 | { 27 | Match match; 28 | if (!string.IsNullOrWhiteSpace(input) && (match = Regex.Match(input)).Success) 29 | { 30 | result = new(match.Groups[1].Value, match.Groups[2].Value); 31 | return true; 32 | } 33 | 34 | result = default; 35 | return false; 36 | } 37 | 38 | public static Either From(string prefix, string suffix) 39 | { 40 | var errorList = new List(2); 41 | 42 | if (prefix.Length != 3 || !int.TryParse(prefix, out _)) 43 | { 44 | errorList.Add("Invalid prefix."); 45 | } 46 | 47 | if (suffix.Length != 7 || !int.TryParse(suffix, out _)) 48 | { 49 | errorList.Add("Invalid suffix."); 50 | } 51 | 52 | return errorList.Count > 0 53 | ? Either.Left(new Error.Invalid(string.Join(" ", errorList))) 54 | : Either.Right(new ItemId(prefix, suffix)); 55 | } 56 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Domain/Cart.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 4 | 5 | public class Cart 6 | { 7 | private readonly Dictionary _items; 8 | 9 | private Cart(CartId cartId, IReadOnlyCollection cartItems) 10 | { 11 | Require.NotDefault(cartId, nameof(cartId)); 12 | Require.NotNull(cartItems, nameof(cartItems)); 13 | 14 | Id = cartId; 15 | _items = cartItems.ToDictionary(i => i.ItemId, i => i); 16 | } 17 | 18 | public static Cart New() 19 | => new Cart(CartId.New(), Array.Empty()); 20 | 21 | public static Cart From(CartId cartId, IReadOnlyCollection cartItems) 22 | => new Cart(cartId, cartItems); 23 | 24 | public CartId Id { get; } 25 | 26 | public IReadOnlyCollection Items => _items.Values; 27 | 28 | public Either AddItemToCart(Item item, int quantity) 29 | { 30 | if (_items.ContainsKey(item.Id)) 31 | { 32 | return Either.Left(new Error.Invalid($"Item {item.Id} already in the cart.")); 33 | } 34 | 35 | var cartItem = new CartItem(item.Id, quantity); 36 | _items.Add(item.Id, cartItem); 37 | return Either.Right(cartItem); 38 | } 39 | 40 | public Either UpdateItemInCart(ItemId itemId, int quantity) 41 | { 42 | if (!_items.ContainsKey(itemId)) 43 | { 44 | return Either.Left(new Error.Invalid($"Item {itemId} not in the cart.")); 45 | } 46 | 47 | var cartItem = new CartItem(itemId, quantity); 48 | _items[itemId] = cartItem; 49 | return Either.Right(cartItem); 50 | } 51 | 52 | public void RemoveItemFromCart(ItemId itemId) 53 | { 54 | _items.Remove(itemId); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Features/ResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Features; 4 | 5 | public static class ResultExtensions 6 | { 7 | public static IResult ToResult(this TResult result) => Results.Ok(result); 8 | 9 | public static IResult ToResult(this Unit result) => Results.NoContent(); 10 | 11 | public static IResult ToResult(this Maybe result, Func valueMapper) 12 | => result 13 | .MapValueOr( 14 | value => Results.Ok(valueMapper(value)), 15 | () => Results.NotFound()); 16 | 17 | public static IResult ToResult(this Either result) 18 | => result 19 | .Fold( 20 | error => error.MapError(), 21 | _ => Results.NoContent()); 22 | 23 | 24 | public static IResult ToResult(this Either result) 25 | => result 26 | .Fold( 27 | error => error.MapError(), 28 | value => Results.Ok(value)); 29 | 30 | 31 | public static IResult ToResult( 32 | this Either result, 33 | Func valueMapper) where TModel : notnull 34 | => result 35 | .Fold( 36 | error => error.MapError(), 37 | value => value is Unit ? Results.NoContent() : Results.Ok(valueMapper(value))); 38 | 39 | private static IResult MapError(this Error error) 40 | => error.Accept( 41 | new ResultMappingVisitor()); 42 | 43 | private readonly struct ResultMappingVisitor : Error.IResultVisitor 44 | { 45 | public IResult Visit(Error.NotFound result) 46 | => Results.NotFound(result.Message); 47 | 48 | public IResult Visit(Error.Invalid result) 49 | => Results.BadRequest(result.Message); 50 | 51 | // public IResult Visit(Error.Unexpected result) 52 | // => Results.StatusCode(500); 53 | } 54 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Shared/StronglyTypedId.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Linq.Expressions; 3 | 4 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 5 | 6 | public abstract class StronglyTypedId 7 | { 8 | protected StronglyTypedId(Guid id) 9 | { 10 | Value = id; 11 | } 12 | 13 | public Guid Value { get; } 14 | 15 | public override int GetHashCode() => Value.GetHashCode(); 16 | public override string ToString() => Value.ToString(); 17 | 18 | public static bool TryParse(string? input, Type stronglyTypedIdType, [MaybeNullWhen(false)] out object result) 19 | { 20 | if (Guid.TryParse(input, out var id)) 21 | { 22 | result = GetConstructor(stronglyTypedIdType)(id); 23 | return true; 24 | } 25 | 26 | result = default; 27 | return false; 28 | } 29 | 30 | private static Func GetConstructor(Type stronglyTypedIdType) 31 | { 32 | // TODO: cache 33 | var ctor = stronglyTypedIdType.GetConstructor(new[] { typeof(Guid) }); 34 | var parameter = Expression.Parameter(typeof(Guid)); 35 | var ctorExpression = Expression.New(ctor!, parameter); 36 | var lambda = Expression.Lambda>(ctorExpression, parameter); 37 | return lambda.Compile(); 38 | } 39 | } 40 | 41 | public abstract class StronglyTypedId : StronglyTypedId, IComparable, 42 | IEquatable 43 | where TStronglyTypedId : StronglyTypedId 44 | { 45 | protected StronglyTypedId(Guid id) : base(id) 46 | { 47 | } 48 | 49 | public bool Equals(TStronglyTypedId? other) => other is not null && Value.Equals(other.Value); 50 | 51 | public int CompareTo(TStronglyTypedId? other) => Value.CompareTo(other?.Value); 52 | 53 | public override bool Equals(object? obj) 54 | { 55 | if (ReferenceEquals(null, obj)) return false; 56 | return obj is TStronglyTypedId other && Equals(other); 57 | } 58 | 59 | public override int GetHashCode() => Value.GetHashCode(); 60 | } 61 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.04.OOifying.Web/Features/Carts/AddItemToCart.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._04.OOifying.Web.Domain; 2 | using OOPsIDidItAgain._04.OOifying.Web.Domain.PostAddItemToCartListeners; 3 | using OOPsIDidItAgain._04.OOifying.Web.Exceptions; 4 | using OOPsIDidItAgain._04.OOifying.Web.Handlers; 5 | 6 | namespace OOPsIDidItAgain._04.OOifying.Web.Features.Carts; 7 | 8 | public static class AddItemToCart 9 | { 10 | public record Request(string CartId, string ItemId, int Quantity) : IRequest; 11 | 12 | public record Response; 13 | 14 | public class Handler : IRequestHandler 15 | { 16 | private readonly ICartRepository _cartRepository; 17 | private readonly IItemRepository _itemRepository; 18 | private readonly IItemSaleRuleRepository _itemSaleRuleRepository; 19 | private readonly IPostAddItemToCartListener _listener; 20 | 21 | public Handler( 22 | ICartRepository cartRepository, 23 | IItemRepository itemRepository, 24 | IItemSaleRuleRepository itemSaleRuleRepository, 25 | IPostAddItemToCartListener listener) 26 | { 27 | _cartRepository = cartRepository; 28 | _itemRepository = itemRepository; 29 | _itemSaleRuleRepository = itemSaleRuleRepository; 30 | _listener = listener; 31 | } 32 | 33 | public Response Handle(Request input) 34 | { 35 | var cart = _cartRepository.Get(input.CartId); 36 | if (cart is null) 37 | { 38 | throw new NotFoundException("Couldn't find the cart"); 39 | } 40 | 41 | var item = _itemRepository.Get(input.ItemId); 42 | if (item is null) 43 | { 44 | throw new NotFoundException("Couldn't find the item"); 45 | } 46 | 47 | var rules = _itemSaleRuleRepository.GetForItem(item.Id); 48 | rules.Validate(cart, item, input.Quantity); 49 | 50 | var cartItem = cart.AddItemToCart(item, input.Quantity); 51 | 52 | _cartRepository.Save(cart); 53 | 54 | _listener.OnAdded(cart, item, cartItem); 55 | 56 | // ... 57 | return new Response(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web/Features/Carts/AddItemToCart.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain; 2 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Domain.PostAddItemToCartListeners; 3 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Handlers; 4 | using OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Shared; 5 | 6 | namespace OOPsIDidItAgain._05.MakeUseOfTypeSafety.Web.Features.Carts; 7 | 8 | public static class AddItemToCart 9 | { 10 | public record Request(CartId CartId, ItemId ItemId, int Quantity) : IRequest; 11 | 12 | public class Handler : IRequestHandler 13 | { 14 | private readonly ICartRepository _cartRepository; 15 | private readonly IItemRepository _itemRepository; 16 | private readonly IItemSaleRuleRepository _itemSaleRuleRepository; 17 | 18 | private readonly IPostAddItemToCartListener _listener; 19 | 20 | public Handler( 21 | ICartRepository cartRepository, 22 | IItemRepository itemRepository, 23 | IItemSaleRuleRepository itemSaleRuleRepository, 24 | IPostAddItemToCartListener listener) 25 | { 26 | _cartRepository = cartRepository; 27 | _itemRepository = itemRepository; 28 | _itemSaleRuleRepository = itemSaleRuleRepository; 29 | _listener = listener; 30 | } 31 | 32 | public Unit Handle(Request input) 33 | { 34 | var maybeCart = _cartRepository.Get(input.CartId); 35 | var cart = maybeCart.ValueOr( 36 | () => throw new DomainException(new ErrorDetail.NotFound("Couldn't find the cart"))); 37 | 38 | var item = _itemRepository.Get(input.ItemId); 39 | if (item is null) 40 | { 41 | throw new DomainException(new ErrorDetail.NotFound("Couldn't find the item")); 42 | } 43 | 44 | var rules = _itemSaleRuleRepository.GetForItem(item.Id); 45 | rules.Validate(cart, item, input.Quantity); 46 | 47 | var cartItem = cart.AddItemToCart(item, input.Quantity); 48 | 49 | _cartRepository.Save(cart); 50 | 51 | _listener.OnAdded(cart, item, cartItem); 52 | 53 | return Unit.Instance; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.PostAddItemToCartListeners; 3 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Features.Carts; 4 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Handlers; 5 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Infrastructure; 6 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.ModelBinding; 7 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Services; 8 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 9 | 10 | var builder = WebApplication.CreateBuilder(args); 11 | 12 | builder.Services.Configure(o => 13 | { 14 | o.SerializerOptions.Converters.Add(new StronglyTypedIdJsonConverter()); 15 | o.SerializerOptions.Converters.Add(new StronglyTypedIdJsonConverter()); 16 | }); 17 | 18 | builder.Services 19 | .AddScoped>, AddItemToCart.Handler>() 20 | .AddScoped>, GetCart.Handler>(); 21 | 22 | builder.Services 23 | .Decorate>, 24 | RequestHandlerLoggingDecorator>>() 25 | .Decorate>, 26 | RequestHandlerLoggingDecorator>>(); 27 | 28 | builder.Services 29 | .AddSingleton() 30 | .AddSingleton() 31 | .AddSingleton() 32 | .AddSingleton() 33 | .AddSingleton( 34 | s => new CompositePostAddItemToCartListener( 35 | new[] 36 | { 37 | new WatchlistNotifierListener( 38 | s.GetRequiredService(), 39 | new[] {((Either.Right) ItemId.From("123", "4567890")).Value}) 40 | })); 41 | 42 | var app = builder.Build(); 43 | 44 | AddItemToCartEndpoint.MapEndpoint(app); 45 | GetCartEndpoint.MapEndpoint(app); 46 | 47 | app.Run(); -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Features/Carts/AddItemToCart2.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.PostAddItemToCartListeners; 3 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Handlers; 4 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 5 | 6 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Features.Carts; 7 | 8 | public static class AddItemToCart2 9 | { 10 | public record Request(CartId CartId, ItemId ItemId, int Quantity) : IRequest>; 11 | 12 | public class Handler : IRequestHandler> 13 | { 14 | private readonly ICartRepository _cartRepository; 15 | private readonly IItemRepository _itemRepository; 16 | private readonly IItemSaleRuleRepository _itemSaleRuleRepository; 17 | 18 | private readonly IPostAddItemToCartListener _listener; 19 | 20 | public Handler( 21 | ICartRepository cartRepository, 22 | IItemRepository itemRepository, 23 | IItemSaleRuleRepository itemSaleRuleRepository, 24 | IPostAddItemToCartListener listener) 25 | { 26 | _cartRepository = cartRepository; 27 | _itemRepository = itemRepository; 28 | _itemSaleRuleRepository = itemSaleRuleRepository; 29 | _listener = listener; 30 | } 31 | 32 | public Either Handle(Request input) 33 | { 34 | var maybeCart = _cartRepository.Get(input.CartId); 35 | if (!maybeCart.TryGetValue(out var cart)) 36 | { 37 | return Either.Left(new Error.NotFound("Couldn't find the cart")); 38 | } 39 | 40 | var item = _itemRepository.Get(input.ItemId); 41 | if (item is null) 42 | { 43 | return Either.Left(new Error.NotFound("Couldn't find the item")); 44 | } 45 | 46 | var rules = _itemSaleRuleRepository.GetForItem(item.Id); 47 | return rules 48 | .Validate(cart, item, input.Quantity) 49 | .FlatMap(_ => cart.AddItemToCart(item, input.Quantity)) 50 | .Do(_ => _cartRepository.Save(cart)) 51 | .Do(cartItem => _listener.OnAdded(cart, item, cartItem)) 52 | .Map(_ => Unit.Instance); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Shared/EitherExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 2 | 3 | public static class EitherExtensions 4 | { 5 | public static TOut Fold( 6 | this Either result, 7 | Func left, 8 | Func right) 9 | { 10 | Require.NotNull(result, nameof(result)); 11 | Require.NotNull(left, nameof(left)); 12 | Require.NotNull(right, nameof(right)); 13 | 14 | return result switch 15 | { 16 | Either.Left error => left(error.Value), 17 | Either.Right success => right(success.Value), 18 | _ => throw CreateUnexpectedResultTypeException(nameof(result)) 19 | }; 20 | } 21 | 22 | public static Either Map( 23 | this Either result, 24 | Func right) 25 | { 26 | Require.NotNull(result, nameof(result)); 27 | Require.NotNull(right, nameof(right)); 28 | 29 | return result switch 30 | { 31 | Either.Left error => Either.Left(error.Value), 32 | Either.Right success => Either.Right(right(success.Value)), 33 | _ => throw CreateUnexpectedResultTypeException(nameof(result)) 34 | }; 35 | } 36 | 37 | public static Either Do( 38 | this Either result, 39 | Action action) 40 | { 41 | Require.NotNull(result, nameof(result)); 42 | Require.NotNull(action, nameof(action)); 43 | 44 | if (result is Either.Right right) 45 | { 46 | action(right.Value); 47 | } 48 | 49 | return result; 50 | } 51 | 52 | public static Either FlatMap( 53 | this Either result, 54 | Func> right) 55 | { 56 | Require.NotNull(result, nameof(result)); 57 | Require.NotNull(right, nameof(right)); 58 | 59 | return result switch 60 | { 61 | Either.Left error => Either.Left(error.Value), 62 | Either.Right success => right(success.Value), 63 | _ => throw CreateUnexpectedResultTypeException(nameof(result)) 64 | }; 65 | } 66 | 67 | private static Exception CreateUnexpectedResultTypeException(string parameterName) 68 | => new ArgumentOutOfRangeException( 69 | parameterName, 70 | "Should never happen -> Either is always Left or Right"); 71 | } 72 | -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.06.MinimizingExceptions.Web/Features/Carts/AddItemToCart.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain; 2 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Domain.PostAddItemToCartListeners; 3 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Handlers; 4 | using OOPsIDidItAgain._06.MinimizingExceptions.Web.Shared; 5 | 6 | namespace OOPsIDidItAgain._06.MinimizingExceptions.Web.Features.Carts; 7 | 8 | public static class AddItemToCart 9 | { 10 | public record Request(CartId CartId, ItemId ItemId, int Quantity) : IRequest>; 11 | 12 | public class Handler : IRequestHandler> 13 | { 14 | private readonly ICartRepository _cartRepository; 15 | private readonly IItemRepository _itemRepository; 16 | private readonly IItemSaleRuleRepository _itemSaleRuleRepository; 17 | 18 | private readonly IPostAddItemToCartListener _listener; 19 | 20 | public Handler( 21 | ICartRepository cartRepository, 22 | IItemRepository itemRepository, 23 | IItemSaleRuleRepository itemSaleRuleRepository, 24 | IPostAddItemToCartListener listener) 25 | { 26 | _cartRepository = cartRepository; 27 | _itemRepository = itemRepository; 28 | _itemSaleRuleRepository = itemSaleRuleRepository; 29 | _listener = listener; 30 | } 31 | 32 | public Either Handle(Request input) 33 | { 34 | var maybeCart = _cartRepository.Get(input.CartId); 35 | if (!maybeCart.TryGetValue(out var cart)) 36 | { 37 | return Either.Left(new Error.NotFound("Couldn't find the cart")); 38 | } 39 | 40 | var item = _itemRepository.Get(input.ItemId); 41 | if (item is null) 42 | { 43 | return Either.Left(new Error.NotFound("Couldn't find the item")); 44 | } 45 | 46 | var rules = _itemSaleRuleRepository.GetForItem(item.Id); 47 | var rulesResult = rules.Validate(cart, item, input.Quantity); 48 | 49 | 50 | if (rulesResult is Either.Left) 51 | { 52 | return rulesResult; 53 | } 54 | 55 | var cartItemResult = cart.AddItemToCart(item, input.Quantity); 56 | 57 | if (cartItemResult is Either.Left cartItemErrorResult) 58 | { 59 | return Either.Left(cartItemErrorResult.Value); 60 | } 61 | 62 | _cartRepository.Save(cart); 63 | 64 | _listener.OnAdded(cart, item, ((Either.Right) cartItemResult).Value); 65 | 66 | return Either.Right(Unit.Instance); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.03.IndividualRequestHandlers.Web/Features/Carts/AddItemToCart.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Data; 2 | using OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Exceptions; 3 | using OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Handlers; 4 | using OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Services; 5 | 6 | namespace OOPsIDidItAgain._03.IndividualRequestHandlers.Web.Features.Carts; 7 | 8 | public static class AddItemToCart 9 | { 10 | public record Request : IRequest 11 | { 12 | public string CartId { get; init; } 13 | 14 | public string ItemId { get; init; } 15 | 16 | public int Quantity { get; init; } 17 | } 18 | 19 | public class Response 20 | { 21 | } 22 | 23 | private class Handler : IRequestHandler 24 | { 25 | private readonly ICartRepository _cartRepository; 26 | private readonly IItemRepository _itemRepository; 27 | private readonly INotifier _notifier; 28 | private readonly ILogger _logger; 29 | 30 | public Handler( 31 | ICartRepository cartRepository, 32 | IItemRepository itemRepository, 33 | INotifier notifier, 34 | ILogger logger) 35 | { 36 | _cartRepository = cartRepository; 37 | _itemRepository = itemRepository; 38 | _notifier = notifier; 39 | _logger = logger; 40 | } 41 | 42 | public Response Handle(Request input) 43 | { 44 | // do some validations 45 | if (input.CartId == default 46 | || input.ItemId == default 47 | || input.Quantity <= 0) 48 | { 49 | throw new ValidationException("Something isn't valid"); 50 | } 51 | 52 | var cart = _cartRepository.Get(input.CartId); 53 | if (cart is null) 54 | { 55 | throw new NotFoundException("Couldn't find the cart"); 56 | } 57 | 58 | var item = _itemRepository.Get(input.ItemId); 59 | if (item is null) 60 | { 61 | throw new NotFoundException("Couldn't find the item"); 62 | } 63 | 64 | var cartItem = cart.Items?.FirstOrDefault(i => i.ItemId == input.ItemId); 65 | if (cartItem != null) 66 | { 67 | // item already on cart 68 | throw new ValidationException("Item already on cart"); 69 | } 70 | 71 | _logger.LogInformation("Checking if can add item to cart"); 72 | 73 | if ((item.MaximumQuantity ?? int.MaxValue) < input.Quantity) 74 | { 75 | throw new ValidationException("Quantity not allowed"); 76 | } 77 | 78 | if ((item.MinimumTimeOfDayToSell ?? TimeSpan.Zero) > DateTime.Now.TimeOfDay) 79 | { 80 | throw new ValidationException("Can't buy that yet!"); 81 | } 82 | 83 | _logger.LogInformation("Adding item to cart"); 84 | 85 | cartItem = new CartItem 86 | { 87 | ItemId = input.ItemId, 88 | Quantity = input.Quantity 89 | }; 90 | 91 | if (cart.Items is null) 92 | { 93 | cart.Items = new[] {cartItem}; 94 | } 95 | else 96 | { 97 | cart.Items = cart.Items.Concat(new[] {cartItem}); 98 | } 99 | 100 | _cartRepository.Save(cart); 101 | 102 | _logger.LogInformation("Checking if need to notify someone"); 103 | if (item.IsInWatchlist) 104 | { 105 | _notifier.Notify(input.ItemId); 106 | } 107 | 108 | // ... 109 | return new Response(); 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Controllers/CartsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using OOPsIDidItAgain._02.SuperService.Web.Data; 3 | using OOPsIDidItAgain._02.SuperService.Web.Exceptions; 4 | using OOPsIDidItAgain._02.SuperService.Web.Models; 5 | using OOPsIDidItAgain._02.SuperService.Web.Services; 6 | 7 | namespace OOPsIDidItAgain._02.SuperService.Web.Controllers; 8 | 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class CartsController : ControllerBase 12 | { 13 | private readonly ICartsService _cartsService; 14 | private readonly ILogger _logger; 15 | 16 | public CartsController( 17 | ICartsService cartsService, 18 | ILogger logger) 19 | { 20 | _cartsService = cartsService; 21 | _logger = logger; 22 | } 23 | 24 | [HttpGet("{cartId}")] 25 | public ActionResult Get(string cartId) 26 | { 27 | var cart = _cartsService.Get(cartId); 28 | if (cart != null) 29 | { 30 | return MapToModel(cart); 31 | } 32 | 33 | return NotFound(); 34 | } 35 | 36 | [HttpPost] 37 | public ActionResult CreateCart() 38 | => MapToModel(_cartsService.CreateCart()); 39 | 40 | 41 | [HttpPost("{cartId}/items")] 42 | public IActionResult AddItemToCart(string cartId, AddItemToCartModel addItemToCart) 43 | { 44 | _logger.LogInformation("Starting {actionName}", nameof(AddItemToCart)); 45 | try 46 | { 47 | _cartsService.AddItemToCart(cartId, addItemToCart.ItemId, addItemToCart.Quantity); 48 | return NoContent(); 49 | } 50 | catch (ValidationException vex) 51 | { 52 | return BadRequest(vex.Message); 53 | } 54 | catch (NotFoundException nex) 55 | { 56 | return NotFound(nex.Message); 57 | } 58 | finally 59 | { 60 | _logger.LogInformation("Ending {actionName}", nameof(AddItemToCart)); 61 | } 62 | } 63 | 64 | [HttpPut("{cartId}/items/{itemId}")] 65 | public IActionResult UpdateItemInCart(string cartId, string itemId, UpdateItemInCart updateItemInCart) 66 | { 67 | _logger.LogInformation("Starting {actionName}", nameof(UpdateItemInCart)); 68 | try 69 | { 70 | _cartsService.UpdateItemInCart(cartId, itemId, updateItemInCart.Quantity); 71 | return NoContent(); 72 | } 73 | catch (ValidationException vex) 74 | { 75 | return BadRequest(vex.Message); 76 | } 77 | catch (NotFoundException nex) 78 | { 79 | return NotFound(nex.Message); 80 | } 81 | finally 82 | { 83 | _logger.LogInformation("Ending {actionName}", nameof(UpdateItemInCart)); 84 | } 85 | } 86 | 87 | [HttpDelete("{cartId}/items/{itemId}")] 88 | public IActionResult RemoveItemFromCart(string cartId, string itemId) 89 | { 90 | _logger.LogInformation("Starting {actionName}", nameof(UpdateItemInCart)); 91 | try 92 | { 93 | _cartsService.RemoveItemFromCart(cartId, itemId); 94 | return NoContent(); 95 | } 96 | catch (ValidationException vex) 97 | { 98 | return BadRequest(vex.Message); 99 | } 100 | catch (NotFoundException nex) 101 | { 102 | return NotFound(nex.Message); 103 | } 104 | finally 105 | { 106 | _logger.LogInformation("Ending {actionName}", nameof(UpdateItemInCart)); 107 | } 108 | } 109 | 110 | private CartModel MapToModel(Cart cart) 111 | => new CartModel 112 | { 113 | Id = cart.Id, 114 | Items = cart.Items?.Select(MapToModel) ?? Enumerable.Empty() 115 | }; 116 | 117 | private CartItemModel MapToModel(CartItem cartItem) 118 | => new CartItemModel 119 | { 120 | ItemId = cartItem.ItemId, 121 | Quantity = cartItem.Quantity 122 | }; 123 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.02.SuperService.Web/Services/CartsService.cs: -------------------------------------------------------------------------------- 1 | using OOPsIDidItAgain._02.SuperService.Web.Data; 2 | using OOPsIDidItAgain._02.SuperService.Web.Exceptions; 3 | 4 | namespace OOPsIDidItAgain._02.SuperService.Web.Services; 5 | 6 | public class CartsService : ICartsService 7 | { 8 | private readonly ICartRepository _cartRepository; 9 | private readonly IItemRepository _itemRepository; 10 | private readonly INotifier _notifier; 11 | private readonly ILogger _logger; 12 | 13 | public CartsService( 14 | ICartRepository cartRepository, 15 | IItemRepository itemRepository, 16 | INotifier notifier, 17 | ILogger logger) 18 | { 19 | _cartRepository = cartRepository; 20 | _itemRepository = itemRepository; 21 | _notifier = notifier; 22 | _logger = logger; 23 | } 24 | 25 | 26 | public Cart Get(string cartId) 27 | => _cartRepository.Get(cartId); 28 | 29 | 30 | public Cart CreateCart() 31 | => _cartRepository.Save(new Cart()); 32 | 33 | 34 | public void AddItemToCart(string cartId, string itemId, int quantity) 35 | { 36 | _logger.LogInformation("Starting {actionName}", nameof(AddItemToCart)); 37 | try 38 | { 39 | // do some validations 40 | if (cartId == default 41 | || itemId == default 42 | || quantity <= 0) 43 | { 44 | throw new ValidationException("Something isn't valid"); 45 | } 46 | 47 | var cart = _cartRepository.Get(cartId); 48 | if (cart is null) 49 | { 50 | throw new NotFoundException("Couldn't find the cart"); 51 | } 52 | 53 | var item = _itemRepository.Get(itemId); 54 | if (item is null) 55 | { 56 | throw new NotFoundException("Couldn't find the item"); 57 | } 58 | 59 | var cartItem = cart.Items?.FirstOrDefault(i => i.ItemId == itemId); 60 | if (cartItem != null) 61 | { 62 | // item already on cart 63 | throw new ValidationException("Item already on cart"); 64 | } 65 | 66 | _logger.LogInformation("Checking if can add item to cart"); 67 | 68 | if ((item.MaximumQuantity ?? int.MaxValue) < quantity) 69 | { 70 | throw new ValidationException("Quantity not allowed"); 71 | } 72 | 73 | if ((item.MinimumTimeOfDayToSell ?? TimeSpan.Zero) > DateTime.Now.TimeOfDay) 74 | { 75 | throw new ValidationException("Can't buy that yet!"); 76 | } 77 | 78 | _logger.LogInformation("Adding item to cart"); 79 | 80 | cartItem = new CartItem 81 | { 82 | ItemId = itemId, 83 | Quantity = quantity 84 | }; 85 | 86 | if (cart.Items is null) 87 | { 88 | cart.Items = new[] {cartItem}; 89 | } 90 | else 91 | { 92 | cart.Items = cart.Items.Concat(new[] {cartItem}); 93 | } 94 | 95 | _cartRepository.Save(cart); 96 | 97 | _logger.LogInformation("Checking if need to notify someone"); 98 | if (item.IsInWatchlist) 99 | { 100 | _notifier.Notify(itemId); 101 | } 102 | 103 | // ... 104 | } 105 | finally 106 | { 107 | _logger.LogInformation("Ending {actionName}", nameof(AddItemToCart)); 108 | } 109 | } 110 | 111 | public void UpdateItemInCart(string cartId, string itemId, int quantity) 112 | { 113 | // do some validations 114 | if (cartId == default 115 | || itemId == default 116 | || quantity <= 0) 117 | { 118 | throw new ValidationException("Something isn't valid"); 119 | } 120 | 121 | var cart = _cartRepository.Get(cartId); 122 | if (cart is null) 123 | { 124 | throw new NotFoundException("Couldn't find the cart"); 125 | } 126 | 127 | var item = _itemRepository.Get(itemId); 128 | if (item is null) 129 | { 130 | throw new NotFoundException("Couldn't find the item"); 131 | } 132 | 133 | var cartItem = cart.Items?.FirstOrDefault(i => i.ItemId == itemId); 134 | if (cartItem is null) 135 | { 136 | throw new NotFoundException("Couldn't find the item on the cart"); 137 | } 138 | 139 | _logger.LogInformation("Checking if can add item to cart"); 140 | if ((item.MaximumQuantity ?? int.MaxValue) < quantity) 141 | { 142 | throw new ValidationException("Quantity not allowed"); 143 | } 144 | 145 | cartItem.Quantity = quantity; 146 | 147 | _cartRepository.Save(cart); 148 | 149 | _logger.LogInformation("Checking if need to notify someone"); 150 | if (item.IsInWatchlist) 151 | { 152 | _notifier.Notify(itemId); 153 | } 154 | 155 | } 156 | 157 | public void RemoveItemFromCart(string cartId, string itemId) 158 | { 159 | // do some validations 160 | if (cartId == default 161 | || itemId == default) 162 | { 163 | throw new ValidationException("Something isn't valid"); 164 | } 165 | 166 | var cart = _cartRepository.Get(cartId); 167 | if (cart is null) 168 | { 169 | throw new NotFoundException("Couldn't find the cart"); 170 | } 171 | 172 | var item = _itemRepository.Get(itemId); 173 | if (item is null) 174 | { 175 | throw new NotFoundException("Couldn't find the item"); 176 | } 177 | 178 | cart.Items = cart.Items.Where(i => i.ItemId != itemId); 179 | 180 | _cartRepository.Save(cart); 181 | 182 | _logger.LogInformation("Checking if need to notify someone"); 183 | if (item.IsInWatchlist) 184 | { 185 | _notifier.Notify(itemId); 186 | } 187 | } 188 | } -------------------------------------------------------------------------------- /src/OOPsIDidItAgain.01.SuperController.Web/Controllers/CartsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using OOPsIDidItAgain._01.SuperController.Web.Data; 3 | using OOPsIDidItAgain._01.SuperController.Web.Models; 4 | using OOPsIDidItAgain._01.SuperController.Web.Services; 5 | 6 | namespace OOPsIDidItAgain._01.SuperController.Web.Controllers; 7 | 8 | [Route("api/[controller]")] 9 | [ApiController] 10 | public class CartsController : ControllerBase 11 | { 12 | private readonly ICartRepository _cartRepository; 13 | private readonly IItemRepository _itemRepository; 14 | private readonly INotifier _notifier; 15 | private readonly ILogger _logger; 16 | 17 | public CartsController( 18 | ICartRepository cartRepository, 19 | IItemRepository itemRepository, 20 | INotifier notifier, 21 | ILogger logger) 22 | { 23 | _cartRepository = cartRepository; 24 | _itemRepository = itemRepository; 25 | _notifier = notifier; 26 | _logger = logger; 27 | } 28 | 29 | [HttpGet("{cartId}")] 30 | public ActionResult Get(string cartId) 31 | { 32 | var cart = _cartRepository.Get(cartId); 33 | if (cart != null) 34 | { 35 | return MapToModel(cart); 36 | } 37 | 38 | return NotFound(); 39 | } 40 | 41 | [HttpPost] 42 | public ActionResult CreateCart() 43 | => MapToModel(_cartRepository.Save(new Cart())); 44 | 45 | 46 | [HttpPost("{cartId}/items")] 47 | public IActionResult AddItemToCart(string cartId, AddItemToCartModel addItemToCart) 48 | { 49 | _logger.LogInformation("Starting {actionName}", nameof(AddItemToCart)); 50 | try 51 | { 52 | // do some validations 53 | if (cartId == default 54 | || addItemToCart.ItemId == default 55 | || addItemToCart.Quantity <= 0) 56 | { 57 | return BadRequest(); 58 | } 59 | 60 | var cart = _cartRepository.Get(cartId); 61 | if (cart is null) 62 | { 63 | return NotFound(); 64 | } 65 | 66 | var item = _itemRepository.Get(addItemToCart.ItemId); 67 | if (item is null) 68 | { 69 | return NotFound(); 70 | } 71 | 72 | var cartItem = cart.Items?.FirstOrDefault(i => i.ItemId == addItemToCart.ItemId); 73 | if (cartItem != null) 74 | { 75 | // item already on cart 76 | return BadRequest(); 77 | } 78 | 79 | _logger.LogInformation("Checking if can add item to cart"); 80 | 81 | if ((item.MaximumQuantity ?? int.MaxValue) < addItemToCart.Quantity) 82 | { 83 | return BadRequest(); 84 | } 85 | 86 | if ((item.MinimumTimeOfDayToSell ?? TimeSpan.Zero) > DateTime.Now.TimeOfDay) 87 | { 88 | return BadRequest(); 89 | } 90 | 91 | _logger.LogInformation("Adding item to cart"); 92 | 93 | cartItem = new CartItem 94 | { 95 | ItemId = addItemToCart.ItemId, 96 | Quantity = addItemToCart.Quantity 97 | }; 98 | 99 | if (cart.Items is null) 100 | { 101 | cart.Items = new[] {cartItem}; 102 | } 103 | else 104 | { 105 | cart.Items = cart.Items.Concat(new[] {cartItem}); 106 | } 107 | 108 | _cartRepository.Save(cart); 109 | 110 | _logger.LogInformation("Checking if need to notify someone"); 111 | if (item.IsInWatchlist) 112 | { 113 | _notifier.Notify(item.Id); 114 | } 115 | 116 | // ... 117 | return NoContent(); 118 | } 119 | finally 120 | { 121 | _logger.LogInformation("Ending {actionName}", nameof(AddItemToCart)); 122 | } 123 | } 124 | 125 | [HttpPut("{cartId}/items/{itemId}")] 126 | public IActionResult UpdateItemInCart(string cartId, string itemId, UpdateItemInCart updateItemInCart) 127 | { 128 | // do some validations 129 | if (cartId == default 130 | || itemId == default 131 | || updateItemInCart.Quantity <= 0) 132 | { 133 | return BadRequest(); 134 | } 135 | 136 | var cart = _cartRepository.Get(cartId); 137 | if (cart is null) 138 | { 139 | return NotFound(); 140 | } 141 | 142 | var item = _itemRepository.Get(itemId); 143 | if (item is null) 144 | { 145 | return NotFound(); 146 | } 147 | 148 | var cartItem = cart.Items?.FirstOrDefault(i => i.ItemId == itemId); 149 | if (cartItem is null) 150 | { 151 | return NotFound(); 152 | } 153 | 154 | _logger.LogInformation("Checking if can add item to cart"); 155 | if ((item.MaximumQuantity ?? int.MaxValue) < updateItemInCart.Quantity) 156 | { 157 | return BadRequest(); 158 | } 159 | 160 | cartItem.Quantity = updateItemInCart.Quantity; 161 | 162 | _cartRepository.Save(cart); 163 | 164 | _logger.LogInformation("Checking if need to notify someone"); 165 | if (item.IsInWatchlist) 166 | { 167 | _notifier.Notify(item.Id); 168 | } 169 | 170 | return NoContent(); 171 | } 172 | 173 | [HttpDelete("{cartId}/items/{itemId}")] 174 | public IActionResult RemoveItemFromCart(string cartId, string itemId) 175 | { 176 | // do some validations 177 | if (cartId == default 178 | || itemId == default) 179 | { 180 | return BadRequest(); 181 | } 182 | 183 | var cart = _cartRepository.Get(cartId); 184 | if (cart is null) 185 | { 186 | return NotFound(); 187 | } 188 | 189 | var item = _itemRepository.Get(itemId); 190 | if (item is null) 191 | { 192 | return NotFound(); 193 | } 194 | 195 | cart.Items = cart.Items.Where(i => i.ItemId != itemId); 196 | 197 | _cartRepository.Save(cart); 198 | 199 | _logger.LogInformation("Checking if need to notify someone"); 200 | if (item.IsInWatchlist) 201 | { 202 | _notifier.Notify(item.Id); 203 | } 204 | 205 | return NoContent(); 206 | } 207 | 208 | private CartModel MapToModel(Cart cart) 209 | => new CartModel 210 | { 211 | Id = cart.Id, 212 | Items = cart.Items?.Select(MapToModel) ?? Enumerable.Empty() 213 | }; 214 | 215 | private CartItemModel MapToModel(CartItem cartItem) 216 | => new CartItemModel 217 | { 218 | ItemId = cartItem.ItemId, 219 | Quantity = cartItem.Quantity 220 | }; 221 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | 332 | .DS_Store -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # for details, check https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ 2 | # inspired by: 3 | # - https://github.com/dotnet/runtime/blob/main/.editorconfig 4 | # - https://github.com/dotnet/aspnetcore/blob/main/.editorconfig 5 | 6 | # top-most EditorConfig file 7 | root = true 8 | 9 | [*.cs] 10 | 11 | # Don't use this. qualifier 12 | dotnet_style_qualification_for_field = false:suggestion 13 | dotnet_style_qualification_for_property = false:suggestion 14 | dotnet_style_qualification_for_method = false:suggestion 15 | dotnet_style_qualification_for_event = false:suggestion 16 | 17 | # use int x = .. over Int32 18 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 19 | 20 | # use int.MaxValue over Int32.MaxValue 21 | dotnet_style_predefined_type_for_member_access = true:suggestion 22 | 23 | # prefix private instance fields 24 | dotnet_naming_rule.private_members_with_underscore.symbols = private_fields 25 | dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore 26 | dotnet_naming_rule.private_members_with_underscore.severity = suggestion 27 | 28 | dotnet_naming_symbols.private_fields.applicable_kinds = field 29 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private 30 | 31 | dotnet_naming_style.prefix_underscore.capitalization = camel_case 32 | dotnet_naming_style.prefix_underscore.required_prefix = _ 33 | 34 | # pascal case constants and static readonly 35 | dotnet_naming_style.constant_or_static_readonly.capitalization = pascal_case 36 | 37 | ## const 38 | dotnet_naming_rule.private_const_fields.symbols = private_const_fields 39 | dotnet_naming_rule.private_const_fields.style = constant_or_static_readonly 40 | dotnet_naming_rule.private_const_fields.severity = suggestion 41 | 42 | dotnet_naming_symbols.private_const_fields.applicable_kinds = field 43 | dotnet_naming_symbols.private_const_fields.required_modifiers = const 44 | 45 | ## static readonly 46 | dotnet_naming_rule.private_static_readonly_fields.symbols = private_static_readonly_fields 47 | dotnet_naming_rule.private_static_readonly_fields.style = constant_or_static_readonly 48 | dotnet_naming_rule.private_static_readonly_fields.severity = suggestion 49 | 50 | dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field 51 | dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static, readonly 52 | 53 | # use expression bodied members when there's a single expression 54 | csharp_style_expression_bodied_constructors = true:suggestion 55 | csharp_style_expression_bodied_methods = true:suggestion 56 | csharp_style_expression_bodied_operators = true:suggestion 57 | csharp_style_expression_bodied_properties = true:suggestion 58 | csharp_style_expression_bodied_indexers = true:suggestion 59 | csharp_style_expression_bodied_accessors = true:suggestion 60 | csharp_style_expression_bodied_lambdas = true:suggestion 61 | csharp_style_expression_bodied_local_functions = true:suggestion 62 | 63 | # misc 64 | csharp_prefer_static_local_function = true:suggestion 65 | csharp_using_directive_placement = outside_namespace:suggestion 66 | csharp_prefer_braces = true:suggestion 67 | csharp_prefer_static_local_function = false:suggestion 68 | csharp_style_prefer_switch_expression = true:suggestion 69 | csharp_style_namespace_declarations = file_scoped:warning 70 | dotnet_style_readonly_field = true:suggestion 71 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 72 | 73 | # CA1047: Do not declare protected member in sealed type 74 | dotnet_diagnostic.CA1047.severity = warning 75 | 76 | # CA1507: Use nameof to express symbol names 77 | dotnet_diagnostic.CA1507.severity = warning 78 | 79 | # CA1725: Parameter names should match base declaration 80 | dotnet_diagnostic.CA1725.severity = suggestion 81 | 82 | # CA1802: Use literals where appropriate 83 | dotnet_diagnostic.CA1802.severity = warning 84 | 85 | # CA1821: Remove empty Finalizers 86 | dotnet_diagnostic.CA1821.severity = warning 87 | 88 | # CA1822: Make member static 89 | dotnet_diagnostic.CA1822.severity = suggestion 90 | 91 | # CA1823: Avoid unused private fields 92 | dotnet_diagnostic.CA1823.severity = warning 93 | 94 | # CA1825: Avoid zero-length array allocations 95 | dotnet_diagnostic.CA1825.severity = warning 96 | 97 | # CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly 98 | dotnet_diagnostic.CA1826.severity = warning 99 | 100 | # CA1827: Do not use Count() or LongCount() when Any() can be used 101 | dotnet_diagnostic.CA1827.severity = warning 102 | 103 | # CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used 104 | dotnet_diagnostic.CA1828.severity = warning 105 | 106 | # CA1829: Use Length/Count property instead of Count() when available 107 | dotnet_diagnostic.CA1829.severity = warning 108 | 109 | # CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder 110 | dotnet_diagnostic.CA1830.severity = warning 111 | 112 | # CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate 113 | # CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate 114 | # CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate 115 | dotnet_diagnostic.CA1831.severity = warning 116 | dotnet_diagnostic.CA1832.severity = warning 117 | dotnet_diagnostic.CA1833.severity = warning 118 | 119 | # CA1834: Consider using 'StringBuilder.Append(char)' when applicable 120 | dotnet_diagnostic.CA1834.severity = warning 121 | 122 | # CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' 123 | dotnet_diagnostic.CA1835.severity = warning 124 | 125 | # CA1836: Prefer IsEmpty over Count 126 | dotnet_diagnostic.CA1836.severity = warning 127 | 128 | # CA1841: Prefer Dictionary.Contains methods 129 | dotnet_diagnostic.CA1841.severity = warning 130 | 131 | # CA1842: Do not use 'WhenAll' with a single task 132 | dotnet_diagnostic.CA1842.severity = warning 133 | 134 | # CA1843: Do not use 'WaitAll' with a single task 135 | dotnet_diagnostic.CA1843.severity = warning 136 | 137 | # CA1845: Use span-based 'string.Concat' 138 | dotnet_diagnostic.CA1845.severity = warning 139 | 140 | # CA1846: Prefer AsSpan over Substring 141 | dotnet_diagnostic.CA1846.severity = warning 142 | 143 | # CA2011: Avoid infinite recursion 144 | dotnet_diagnostic.CA2011.severity = warning 145 | 146 | # CA2012: Use ValueTask correctly 147 | dotnet_diagnostic.CA2012.severity = warning 148 | 149 | # CA2013: Do not use ReferenceEquals with value types 150 | dotnet_diagnostic.CA2013.severity = warning 151 | 152 | # CA2016: Forward the 'CancellationToken' parameter to methods that take one 153 | dotnet_diagnostic.CA2016.severity = warning 154 | 155 | # CA2200: Rethrow to preserve stack details 156 | dotnet_diagnostic.CA2200.severity = warning 157 | 158 | # CA2208: Instantiate argument exceptions correctly 159 | dotnet_diagnostic.CA2208.severity = warning 160 | 161 | # IDE0035: Remove unreachable code 162 | dotnet_diagnostic.IDE0035.severity = warning 163 | 164 | # IDE0036: Order modifiers 165 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 166 | dotnet_diagnostic.IDE0036.severity = warning 167 | 168 | # IDE0043: Format string contains invalid placeholder 169 | dotnet_diagnostic.IDE0043.severity = warning 170 | -------------------------------------------------------------------------------- /OOPsIDidItAgain.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F5170B77-169F-4CBC-B008-DC898E80EE8B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OOPsIDidItAgain.03.IndividualRequestHandlers.Web", "src\OOPsIDidItAgain.03.IndividualRequestHandlers.Web\OOPsIDidItAgain.03.IndividualRequestHandlers.Web.csproj", "{A50B1B0F-2B45-4401-B6C5-2071582BFE82}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OOPsIDidItAgain.04.OOifying.Web", "src\OOPsIDidItAgain.04.OOifying.Web\OOPsIDidItAgain.04.OOifying.Web.csproj", "{3BA58D93-2DB1-4F99-9B79-732C6465E1E4}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web", "src\OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web\OOPsIDidItAgain.05.MakeUseOfTypeSafety.Web.csproj", "{272D3D93-5168-443B-BBB4-4C78B067D63F}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OOPsIDidItAgain.06.MinimizingExceptions.Web", "src\OOPsIDidItAgain.06.MinimizingExceptions.Web\OOPsIDidItAgain.06.MinimizingExceptions.Web.csproj", "{0FC110CC-DA39-4F50-8345-F76450205D2B}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OtherStuff", "OtherStuff", "{415D9C32-9E87-4165-A5F6-6B8165ACA999}" 17 | ProjectSection(SolutionItems) = preProject 18 | sample-requests.http = sample-requests.http 19 | EndProjectSection 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OOPsIDidItAgain.01.SuperController.Web", "src\OOPsIDidItAgain.01.SuperController.Web\OOPsIDidItAgain.01.SuperController.Web.csproj", "{A0E082BE-E031-420D-A77C-32D2314B6D3C}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OOPsIDidItAgain.02.SuperService.Web", "src\OOPsIDidItAgain.02.SuperService.Web\OOPsIDidItAgain.02.SuperService.Web.csproj", "{17B74A16-D6A1-4659-A960-E91FF1CFC59C}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Debug|x64 = Debug|x64 29 | Debug|x86 = Debug|x86 30 | Release|Any CPU = Release|Any CPU 31 | Release|x64 = Release|x64 32 | Release|x86 = Release|x86 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Debug|x64.ActiveCfg = Debug|Any CPU 41 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Debug|x64.Build.0 = Debug|Any CPU 42 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Debug|x86.ActiveCfg = Debug|Any CPU 43 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Debug|x86.Build.0 = Debug|Any CPU 44 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Release|x64.ActiveCfg = Release|Any CPU 47 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Release|x64.Build.0 = Release|Any CPU 48 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Release|x86.ActiveCfg = Release|Any CPU 49 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82}.Release|x86.Build.0 = Release|Any CPU 50 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Debug|x64.ActiveCfg = Debug|Any CPU 53 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Debug|x64.Build.0 = Debug|Any CPU 54 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Debug|x86.ActiveCfg = Debug|Any CPU 55 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Debug|x86.Build.0 = Debug|Any CPU 56 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Release|x64.ActiveCfg = Release|Any CPU 59 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Release|x64.Build.0 = Release|Any CPU 60 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Release|x86.ActiveCfg = Release|Any CPU 61 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4}.Release|x86.Build.0 = Release|Any CPU 62 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Debug|x64.ActiveCfg = Debug|Any CPU 65 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Debug|x64.Build.0 = Debug|Any CPU 66 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Debug|x86.ActiveCfg = Debug|Any CPU 67 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Debug|x86.Build.0 = Debug|Any CPU 68 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Release|x64.ActiveCfg = Release|Any CPU 71 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Release|x64.Build.0 = Release|Any CPU 72 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Release|x86.ActiveCfg = Release|Any CPU 73 | {272D3D93-5168-443B-BBB4-4C78B067D63F}.Release|x86.Build.0 = Release|Any CPU 74 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Debug|x64.ActiveCfg = Debug|Any CPU 77 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Debug|x64.Build.0 = Debug|Any CPU 78 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Debug|x86.ActiveCfg = Debug|Any CPU 79 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Debug|x86.Build.0 = Debug|Any CPU 80 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Release|x64.ActiveCfg = Release|Any CPU 83 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Release|x64.Build.0 = Release|Any CPU 84 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Release|x86.ActiveCfg = Release|Any CPU 85 | {0FC110CC-DA39-4F50-8345-F76450205D2B}.Release|x86.Build.0 = Release|Any CPU 86 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 87 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Debug|Any CPU.Build.0 = Debug|Any CPU 88 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Debug|x64.ActiveCfg = Debug|Any CPU 89 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Debug|x64.Build.0 = Debug|Any CPU 90 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Debug|x86.ActiveCfg = Debug|Any CPU 91 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Debug|x86.Build.0 = Debug|Any CPU 92 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Release|Any CPU.ActiveCfg = Release|Any CPU 93 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Release|Any CPU.Build.0 = Release|Any CPU 94 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Release|x64.ActiveCfg = Release|Any CPU 95 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Release|x64.Build.0 = Release|Any CPU 96 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Release|x86.ActiveCfg = Release|Any CPU 97 | {A0E082BE-E031-420D-A77C-32D2314B6D3C}.Release|x86.Build.0 = Release|Any CPU 98 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 99 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Debug|Any CPU.Build.0 = Debug|Any CPU 100 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Debug|x64.ActiveCfg = Debug|Any CPU 101 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Debug|x64.Build.0 = Debug|Any CPU 102 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Debug|x86.ActiveCfg = Debug|Any CPU 103 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Debug|x86.Build.0 = Debug|Any CPU 104 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Release|Any CPU.ActiveCfg = Release|Any CPU 105 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Release|Any CPU.Build.0 = Release|Any CPU 106 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Release|x64.ActiveCfg = Release|Any CPU 107 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Release|x64.Build.0 = Release|Any CPU 108 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Release|x86.ActiveCfg = Release|Any CPU 109 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C}.Release|x86.Build.0 = Release|Any CPU 110 | EndGlobalSection 111 | GlobalSection(NestedProjects) = preSolution 112 | {A50B1B0F-2B45-4401-B6C5-2071582BFE82} = {F5170B77-169F-4CBC-B008-DC898E80EE8B} 113 | {3BA58D93-2DB1-4F99-9B79-732C6465E1E4} = {F5170B77-169F-4CBC-B008-DC898E80EE8B} 114 | {272D3D93-5168-443B-BBB4-4C78B067D63F} = {F5170B77-169F-4CBC-B008-DC898E80EE8B} 115 | {0FC110CC-DA39-4F50-8345-F76450205D2B} = {F5170B77-169F-4CBC-B008-DC898E80EE8B} 116 | {A0E082BE-E031-420D-A77C-32D2314B6D3C} = {F5170B77-169F-4CBC-B008-DC898E80EE8B} 117 | {17B74A16-D6A1-4659-A960-E91FF1CFC59C} = {F5170B77-169F-4CBC-B008-DC898E80EE8B} 118 | EndGlobalSection 119 | EndGlobal 120 | --------------------------------------------------------------------------------