├── .gitattributes ├── .gitignore ├── Application ├── Application.csproj ├── Customers │ └── Queries │ │ └── GetCustomerList │ │ ├── CustomerModel.cs │ │ ├── GetCustomersListQuery.cs │ │ ├── GetCustomersListQueryTests.cs │ │ └── IGetCustomersListQuery.cs ├── Employees │ └── Queries │ │ └── GetEmployeesList │ │ ├── EmployeeModel.cs │ │ ├── GetEmployeesListQueryTests.cs │ │ ├── GetEmployeesQuery.cs │ │ └── IGetEmployeesListQuery.cs ├── Interfaces │ ├── IDatabaseService.cs │ └── IInventoryService.cs ├── Products │ └── Queries │ │ └── GetProductsList │ │ ├── GetProductsListQuery.cs │ │ ├── GetProductsListQueryTests.cs │ │ ├── IGetProductsListQuery.cs │ │ └── ProductModel.cs └── Sales │ ├── Commands │ └── CreateSale │ │ ├── CreateSaleCommand.cs │ │ ├── CreateSaleCommandTests.cs │ │ ├── CreateSaleModel.cs │ │ ├── Factory │ │ ├── ISaleFactory.cs │ │ ├── SaleFactory.cs │ │ └── SaleFactoryTests.cs │ │ └── ICreateSaleCommand.cs │ └── Queries │ ├── GetSaleDetail │ ├── GetSaleDetailQuery.cs │ ├── GetSaleDetailQueryTests.cs │ ├── IGetSaleDetailQuery.cs │ └── SaleDetailModel.cs │ └── GetSalesList │ ├── GetSalesListQuery.cs │ ├── GetSalesListQueryTests.cs │ ├── IGetSalesListQuery.cs │ └── SalesListItemModel.cs ├── CleanArchitecture.sln ├── Common ├── Common.csproj └── Dates │ ├── DateService.cs │ └── IDateService.cs ├── Diagrams ├── Commands.dgml ├── Commands.png ├── Domain.dgml ├── Domain.png ├── Microservice.dgml ├── Microservices.png ├── Queries.dgml ├── Queries.png ├── Solution.dgml ├── Solution.png ├── Tests.dgml └── Tests.png ├── Domain ├── Common │ └── IEntity.cs ├── Customers │ ├── Customer.cs │ └── CustomerTests.cs ├── Domain.csproj ├── Employees │ ├── Employee.cs │ └── EmployeeTests.cs ├── Products │ ├── Product.cs │ └── ProductTests.cs └── Sales │ ├── Sale.cs │ └── SaleTests.cs ├── Infrastructure ├── Infrastructure.csproj ├── Inventory │ ├── InventoryService.cs │ └── InventoryServiceTests.cs └── Network │ ├── HttpClientWrapper.cs │ └── IHttpClientWrapper.cs ├── LICENSE.txt ├── Persistence ├── Customers │ └── CustomerConfiguration.cs ├── DatabaseService.cs ├── Employees │ └── EmployeeConfiguration.cs ├── Persistence.csproj ├── Products │ └── ProductConfiguration.cs └── Sales │ └── SaleConfiguration.cs ├── Presentation ├── Content │ ├── bootstrap.css │ └── site.css ├── CustomViewLocationExpander.cs ├── Customers │ ├── CustomersController.cs │ ├── CustomersControllerTests.cs │ └── Views │ │ └── Index.cshtml ├── Employees │ ├── EmployeesController.cs │ ├── EmployeesControllerTests.cs │ └── Views │ │ └── Index.cshtml ├── Home │ ├── HomeController.cs │ ├── HomeControllerTests.cs │ └── Views │ │ └── Index.cshtml ├── Presentation.csproj ├── Products │ ├── ProductsController.cs │ ├── ProductsControllerTests.cs │ └── Views │ │ └── Index.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── Sales │ ├── Models │ │ └── CreateSaleViewModel.cs │ ├── SalesController.cs │ ├── SalesControllerTests.cs │ ├── Services │ │ ├── CreateSaleViewModelFactory.cs │ │ ├── CreateSaleViewModelFactoryTests.cs │ │ └── ICreateSaleViewModelFactory.cs │ └── Views │ │ ├── Create.cshtml │ │ ├── Detail.cshtml │ │ └── Index.cshtml ├── Shared │ └── Views │ │ ├── Errors.cshtml │ │ └── _Layout.cshtml ├── _ViewStart.cshtml ├── appsettings.Development.json └── appsettings.json ├── README.md ├── Service ├── Customers │ ├── CustomersController.cs │ └── CustomersControllerTests.cs ├── Employees │ ├── EmployeesController.cs │ └── EmployeesControllerTests.cs ├── LowercaseDocumentFilter.cs ├── Products │ ├── ProductsController.cs │ └── ProductsControllerTests.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Sales │ ├── SalesController.cs │ └── SalesControllerTests.cs ├── Service.csproj ├── appsettings.Development.json └── appsettings.json └── Specification ├── Customers └── GetCustomersList │ ├── GetCustomersList.feature │ ├── GetCustomersList.feature.cs │ └── GetCustomersListSteps.cs ├── Employees └── GetEmployeesList │ ├── GetEmployeesList.feature │ ├── GetEmployeesList.feature.cs │ └── GetEmployeesListSteps.cs ├── Products ├── GetProductsList.feature ├── GetProductsList.feature.cs └── GetProductsListSteps.cs ├── Sales ├── CreateASale │ ├── CreateASale.feature │ ├── CreateASale.feature.cs │ ├── CreateASaleSteps.cs │ ├── CreateSaleInfoModel.cs │ ├── CreateSaleOccurredNotificationModel.cs │ └── CreateSaleRecordModel.cs ├── GetSaleDetails │ ├── GetSaleDetails.feature │ ├── GetSaleDetails.feature.cs │ ├── GetSaleDetailsModel.cs │ └── GetSaleDetailsSteps.cs └── GetSalesList │ ├── GetSalesList.feature │ ├── GetSalesList.feature.cs │ ├── GetSalesListModel.cs │ └── GetSalesListSteps.cs ├── Shared ├── AppContext.cs ├── DatabaseLookup.cs └── MockDatabaseService.cs └── Specification.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | *.suo 4 | *.user 5 | *.DotSettings 6 | 7 | /packages/ 8 | !packages/repositories.config 9 | 10 | _NCrunch_* 11 | *.ncrunchproject 12 | *.ncrunchsolution 13 | 14 | .vs/ 15 | -------------------------------------------------------------------------------- /Application/Application.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | disable 7 | CleanArchitecture.$(MSBuildProjectName) 8 | CleanArchitecture.$(MSBuildProjectName.Replace(" ", "_")) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Application/Customers/Queries/GetCustomerList/CustomerModel.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitecture.Application.Customers.Queries.GetCustomerList 2 | { 3 | public class CustomerModel 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Name { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /Application/Customers/Queries/GetCustomerList/GetCustomersListQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Interfaces; 5 | 6 | namespace CleanArchitecture.Application.Customers.Queries.GetCustomerList 7 | { 8 | public class GetCustomersListQuery 9 | : IGetCustomersListQuery 10 | { 11 | private readonly IDatabaseService _database; 12 | 13 | public GetCustomersListQuery(IDatabaseService database) 14 | { 15 | _database = database; 16 | } 17 | 18 | public List Execute() 19 | { 20 | var customers = _database.Customers 21 | .Select(p => new CustomerModel() 22 | { 23 | Id = p.Id, 24 | Name = p.Name 25 | }); 26 | 27 | return customers.ToList(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Application/Customers/Queries/GetCustomerList/GetCustomersListQueryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moq.AutoMock; 5 | using Moq.EntityFrameworkCore; 6 | using CleanArchitecture.Application.Interfaces; 7 | using CleanArchitecture.Domain.Customers; 8 | using NUnit.Framework; 9 | 10 | namespace CleanArchitecture.Application.Customers.Queries.GetCustomerList 11 | { 12 | [TestFixture] 13 | public class GetCustomersListQueryTests 14 | { 15 | private GetCustomersListQuery _query; 16 | private AutoMocker _mocker; 17 | private Customer _customer; 18 | 19 | private const int Id = 1; 20 | private const string Name = "Customer 1"; 21 | 22 | [SetUp] 23 | public void SetUp() 24 | { 25 | _mocker = new AutoMocker(); 26 | 27 | _customer = new Customer() 28 | { 29 | Id = Id, 30 | Name = Name 31 | }; 32 | 33 | _mocker.GetMock() 34 | .Setup(p => p.Customers) 35 | .ReturnsDbSet(new List { _customer }); 36 | 37 | _query = _mocker.CreateInstance(); 38 | } 39 | 40 | [Test] 41 | public void TestExecuteShouldReturnListOfCustomers() 42 | { 43 | var results = _query.Execute(); 44 | 45 | var result = results.Single(); 46 | 47 | Assert.That(result.Id, Is.EqualTo(Id)); 48 | Assert.That(result.Name, Is.EqualTo(Name)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Application/Customers/Queries/GetCustomerList/IGetCustomersListQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CleanArchitecture.Application.Customers.Queries.GetCustomerList 4 | { 5 | public interface IGetCustomersListQuery 6 | { 7 | List Execute(); 8 | } 9 | } -------------------------------------------------------------------------------- /Application/Employees/Queries/GetEmployeesList/EmployeeModel.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitecture.Application.Employees.Queries.GetEmployeesList 2 | { 3 | public class EmployeeModel 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Name { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /Application/Employees/Queries/GetEmployeesList/GetEmployeesListQueryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moq.AutoMock; 5 | using Moq.EntityFrameworkCore; 6 | using CleanArchitecture.Application.Interfaces; 7 | using CleanArchitecture.Domain.Employees; 8 | using NUnit.Framework; 9 | 10 | namespace CleanArchitecture.Application.Employees.Queries.GetEmployeesList 11 | { 12 | [TestFixture] 13 | public class GetEmployeesListQueryTests 14 | { 15 | private GetEmployeesListQuery _query; 16 | private AutoMocker _mocker; 17 | private Employee _employee; 18 | 19 | private const int Id = 1; 20 | private const string Name = "Employee 1"; 21 | 22 | [SetUp] 23 | public void SetUp() 24 | { 25 | _mocker = new AutoMocker(); 26 | 27 | _employee = new Employee() 28 | { 29 | Id = Id, 30 | Name = Name 31 | }; 32 | 33 | _mocker.GetMock() 34 | .Setup(p => p.Employees) 35 | .ReturnsDbSet(new List { _employee }); 36 | 37 | _query = _mocker.CreateInstance(); 38 | } 39 | 40 | [Test] 41 | public void TestExecuteShouldReturnListOfEmployees() 42 | { 43 | var results = _query.Execute(); 44 | 45 | var result = results.Single(); 46 | 47 | Assert.That(result.Id, Is.EqualTo(Id)); 48 | Assert.That(result.Name, Is.EqualTo(Name)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Application/Employees/Queries/GetEmployeesList/GetEmployeesQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Interfaces; 5 | 6 | namespace CleanArchitecture.Application.Employees.Queries.GetEmployeesList 7 | { 8 | public class GetEmployeesListQuery 9 | : IGetEmployeesListQuery 10 | { 11 | private readonly IDatabaseService _database; 12 | 13 | public GetEmployeesListQuery(IDatabaseService database) 14 | { 15 | _database = database; 16 | } 17 | 18 | public List Execute() 19 | { 20 | var employees = _database.Employees 21 | .Select(p => new EmployeeModel 22 | { 23 | Id = p.Id, 24 | Name = p.Name 25 | }); 26 | 27 | return employees.ToList(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Application/Employees/Queries/GetEmployeesList/IGetEmployeesListQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CleanArchitecture.Application.Employees.Queries.GetEmployeesList 4 | { 5 | public interface IGetEmployeesListQuery 6 | { 7 | List Execute(); 8 | } 9 | } -------------------------------------------------------------------------------- /Application/Interfaces/IDatabaseService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using CleanArchitecture.Domain.Customers; 3 | using CleanArchitecture.Domain.Employees; 4 | using CleanArchitecture.Domain.Products; 5 | using CleanArchitecture.Domain.Sales; 6 | 7 | namespace CleanArchitecture.Application.Interfaces 8 | { 9 | public interface IDatabaseService 10 | { 11 | DbSet Customers { get; set; } 12 | 13 | DbSet Employees { get; set; } 14 | 15 | DbSet Products { get; set; } 16 | 17 | DbSet Sales { get; set; } 18 | 19 | void Save(); 20 | } 21 | } -------------------------------------------------------------------------------- /Application/Interfaces/IInventoryService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Application.Interfaces 6 | { 7 | public interface IInventoryService 8 | { 9 | void NotifySaleOccurred(int productId, int quantity); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Application/Products/Queries/GetProductsList/GetProductsListQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Interfaces; 5 | 6 | namespace CleanArchitecture.Application.Products.Queries.GetProductsList 7 | { 8 | public class GetProductsListQuery 9 | : IGetProductsListQuery 10 | { 11 | private readonly IDatabaseService _database; 12 | 13 | public GetProductsListQuery(IDatabaseService database) 14 | { 15 | _database = database; 16 | } 17 | 18 | public List Execute() 19 | { 20 | var products = _database.Products 21 | .Select(p => new ProductModel 22 | { 23 | Id = p.Id, 24 | Name = p.Name, 25 | UnitPrice = p.Price 26 | }); 27 | 28 | return products.ToList(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Application/Products/Queries/GetProductsList/GetProductsListQueryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moq.AutoMock; 5 | using Moq.EntityFrameworkCore; 6 | using CleanArchitecture.Application.Interfaces; 7 | using CleanArchitecture.Domain.Products; 8 | using NUnit.Framework; 9 | 10 | namespace CleanArchitecture.Application.Products.Queries.GetProductsList 11 | { 12 | [TestFixture] 13 | public class GetProductsListQueryTests 14 | { 15 | private GetProductsListQuery _query; 16 | private AutoMocker _mocker; 17 | private Product _product; 18 | 19 | private const int Id = 1; 20 | private const string Name = "Product 1"; 21 | 22 | [SetUp] 23 | public void SetUp() 24 | { 25 | _mocker = new AutoMocker(); 26 | 27 | _product = new Product() 28 | { 29 | Id = Id, 30 | Name = Name 31 | }; 32 | 33 | _mocker.GetMock() 34 | .Setup(p => p.Products) 35 | .ReturnsDbSet(new List { _product }); 36 | 37 | _query = _mocker.CreateInstance(); 38 | } 39 | 40 | [Test] 41 | public void TestExecuteShouldReturnListOfProducts() 42 | { 43 | var results = _query.Execute(); 44 | 45 | var result = results.Single(); 46 | 47 | Assert.That(result.Id, Is.EqualTo(Id)); 48 | Assert.That(result.Name, Is.EqualTo(Name)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Application/Products/Queries/GetProductsList/IGetProductsListQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CleanArchitecture.Application.Products.Queries.GetProductsList 4 | { 5 | public interface IGetProductsListQuery 6 | { 7 | List Execute(); 8 | } 9 | } -------------------------------------------------------------------------------- /Application/Products/Queries/GetProductsList/ProductModel.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitecture.Application.Products.Queries.GetProductsList 2 | { 3 | public class ProductModel 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Name { get; set; } 8 | 9 | public decimal UnitPrice { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /Application/Sales/Commands/CreateSale/CreateSaleCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CleanArchitecture.Application.Interfaces; 4 | using CleanArchitecture.Application.Sales.Commands.CreateSale.Factory; 5 | using CleanArchitecture.Common.Dates; 6 | 7 | namespace CleanArchitecture.Application.Sales.Commands.CreateSale 8 | { 9 | public class CreateSaleCommand 10 | : ICreateSaleCommand 11 | { 12 | private readonly IDateService _dateService; 13 | private readonly IDatabaseService _database; 14 | private readonly ISaleFactory _factory; 15 | private readonly IInventoryService _inventory; 16 | 17 | public CreateSaleCommand( 18 | IDateService dateService, 19 | IDatabaseService database, 20 | ISaleFactory factory, 21 | IInventoryService inventory) 22 | { 23 | _dateService = dateService; 24 | _database = database; 25 | _factory = factory; 26 | _inventory = inventory; 27 | } 28 | 29 | public void Execute(CreateSaleModel model) 30 | { 31 | var date = _dateService.GetDate(); 32 | 33 | var customer = _database.Customers 34 | .Single(p => p.Id == model.CustomerId); 35 | 36 | var employee = _database.Employees 37 | .Single(p => p.Id == model.EmployeeId); 38 | 39 | var product = _database.Products 40 | .Single(p => p.Id == model.ProductId); 41 | 42 | var quantity = model.Quantity; 43 | 44 | var sale = _factory.Create( 45 | date, 46 | customer, 47 | employee, 48 | product, 49 | quantity); 50 | 51 | _database.Sales.Add(sale); 52 | 53 | _database.Save(); 54 | 55 | _inventory.NotifySaleOccurred(product.Id, quantity); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Application/Sales/Commands/CreateSale/CreateSaleCommandTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore; 4 | using System.Linq; 5 | using Moq.AutoMock; 6 | using CleanArchitecture.Application.Interfaces; 7 | using CleanArchitecture.Application.Sales.Commands.CreateSale.Factory; 8 | using CleanArchitecture.Common.Dates; 9 | using CleanArchitecture.Domain.Customers; 10 | using CleanArchitecture.Domain.Employees; 11 | using CleanArchitecture.Domain.Products; 12 | using CleanArchitecture.Domain.Sales; 13 | using Moq; 14 | using Moq.EntityFrameworkCore; 15 | using NUnit.Framework; 16 | 17 | namespace CleanArchitecture.Application.Sales.Commands.CreateSale 18 | { 19 | [TestFixture] 20 | public class CreateSaleCommandTests 21 | { 22 | private CreateSaleCommand _command; 23 | private AutoMocker _mocker; 24 | private CreateSaleModel _model; 25 | private Sale _sale; 26 | 27 | private static readonly DateTime Date = new(2001, 2, 3); 28 | private const int CustomerId = 1; 29 | private const int EmployeeId = 2; 30 | private const int ProductId = 3; 31 | private const decimal UnitPrice = 1.23m; 32 | private const int Quantity = 4; 33 | 34 | [SetUp] 35 | public void SetUp() 36 | { 37 | var customer = new Customer 38 | { 39 | Id = CustomerId 40 | }; 41 | 42 | var employee = new Employee 43 | { 44 | Id = EmployeeId 45 | }; 46 | 47 | var product = new Product 48 | { 49 | Id = ProductId, 50 | Price = UnitPrice 51 | }; 52 | 53 | _model = new CreateSaleModel() 54 | { 55 | CustomerId = CustomerId, 56 | EmployeeId = EmployeeId, 57 | ProductId = ProductId, 58 | Quantity = Quantity 59 | }; 60 | 61 | _sale = new Sale(); 62 | 63 | _mocker = new AutoMocker(); 64 | 65 | _mocker.GetMock() 66 | .Setup(p => p.GetDate()) 67 | .Returns(Date); 68 | 69 | _mocker.GetMock() 70 | .Setup(p => p.Customers) 71 | .ReturnsDbSet(new List { customer }); 72 | 73 | _mocker.GetMock() 74 | .Setup(p => p.Employees) 75 | .ReturnsDbSet(new List { employee }); 76 | 77 | _mocker.GetMock() 78 | .Setup(p => p.Products) 79 | .ReturnsDbSet(new List { product }); 80 | 81 | _mocker.GetMock() 82 | .Setup(p => p.Sales) 83 | .Returns(_mocker.GetMock>().Object); 84 | 85 | _mocker.GetMock() 86 | .Setup(p => p.Create( 87 | Date, 88 | customer, 89 | employee, 90 | product, 91 | Quantity)) 92 | .Returns(_sale); 93 | 94 | _command = _mocker.CreateInstance(); 95 | } 96 | 97 | [Test] 98 | public void TestExecuteShouldAddSaleToTheDatabase() 99 | { 100 | _command.Execute(_model); 101 | 102 | _mocker.GetMock>() 103 | .Verify(p => p.Add(_sale), 104 | Times.Once); 105 | } 106 | 107 | [Test] 108 | public void TestExecuteShouldSaveChangesToDatabase() 109 | { 110 | _command.Execute(_model); 111 | 112 | _mocker.GetMock() 113 | .Verify(p => p.Save(), 114 | Times.Once); 115 | } 116 | 117 | [Test] 118 | public void TestExecuteShouldNotifyInventoryThatSaleOccurred() 119 | { 120 | _command.Execute(_model); 121 | 122 | _mocker.GetMock() 123 | .Verify(p => p.NotifySaleOccurred( 124 | ProductId, 125 | Quantity), 126 | Times.Once); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Application/Sales/Commands/CreateSale/CreateSaleModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Application.Sales.Commands.CreateSale 6 | { 7 | public class CreateSaleModel 8 | { 9 | public int CustomerId { get; set; } 10 | 11 | public int EmployeeId { get; set; } 12 | 13 | public int ProductId { get; set; } 14 | 15 | public int Quantity { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Application/Sales/Commands/CreateSale/Factory/ISaleFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Domain.Customers; 5 | using CleanArchitecture.Domain.Employees; 6 | using CleanArchitecture.Domain.Products; 7 | using CleanArchitecture.Domain.Sales; 8 | 9 | namespace CleanArchitecture.Application.Sales.Commands.CreateSale.Factory 10 | { 11 | public interface ISaleFactory 12 | { 13 | Sale Create(DateTime date, Customer customer, Employee employee, Product product, int quantity); 14 | } 15 | } -------------------------------------------------------------------------------- /Application/Sales/Commands/CreateSale/Factory/SaleFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Domain.Customers; 5 | using CleanArchitecture.Domain.Employees; 6 | using CleanArchitecture.Domain.Products; 7 | using CleanArchitecture.Domain.Sales; 8 | 9 | namespace CleanArchitecture.Application.Sales.Commands.CreateSale.Factory 10 | { 11 | public class SaleFactory : ISaleFactory 12 | { 13 | public Sale Create(DateTime date, Customer customer, Employee employee, Product product, int quantity) 14 | { 15 | var sale = new Sale(); 16 | 17 | sale.Date = date; 18 | 19 | sale.Customer = customer; 20 | 21 | sale.Employee = employee; 22 | 23 | sale.Product = product; 24 | 25 | sale.UnitPrice = sale.Product.Price; 26 | 27 | sale.Quantity = quantity; 28 | 29 | // Note: Total price is calculated in domain logic 30 | 31 | return sale; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Application/Sales/Commands/CreateSale/Factory/SaleFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Domain.Customers; 5 | using CleanArchitecture.Domain.Employees; 6 | using CleanArchitecture.Domain.Products; 7 | using NUnit.Framework; 8 | 9 | namespace CleanArchitecture.Application.Sales.Commands.CreateSale.Factory 10 | { 11 | [TestFixture] 12 | public class SaleFactoryTests 13 | { 14 | private SaleFactory _factory; 15 | private Customer _customer; 16 | private Employee _employee; 17 | private Product _product; 18 | 19 | private static readonly DateTime DateTime = new DateTime(2001, 2, 3); 20 | private const int Quantity = 123; 21 | private const decimal Price = 1.00m; 22 | 23 | [SetUp] 24 | public void SetUp() 25 | { 26 | _customer = new Customer(); 27 | 28 | _employee = new Employee(); 29 | 30 | _product = new Product 31 | { 32 | Price = Price 33 | }; 34 | 35 | _factory = new SaleFactory(); 36 | } 37 | 38 | [Test] 39 | public void TestCreateShouldCreateSale() 40 | { 41 | var result = _factory.Create(DateTime, _customer, _employee, _product, Quantity); 42 | 43 | Assert.That(result.Date, Is.EqualTo(DateTime)); 44 | Assert.That(result.Customer, Is.EqualTo(_customer)); 45 | Assert.That(result.Employee, Is.EqualTo(_employee)); 46 | Assert.That(result.Product, Is.EqualTo(_product)); 47 | Assert.That(result.UnitPrice, Is.EqualTo(Price)); 48 | Assert.That(result.Quantity, Is.EqualTo(Quantity)); 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Application/Sales/Commands/CreateSale/ICreateSaleCommand.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitecture.Application.Sales.Commands.CreateSale 2 | { 3 | public interface ICreateSaleCommand 4 | { 5 | void Execute(CreateSaleModel model); 6 | } 7 | } -------------------------------------------------------------------------------- /Application/Sales/Queries/GetSaleDetail/GetSaleDetailQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Interfaces; 5 | 6 | namespace CleanArchitecture.Application.Sales.Queries.GetSaleDetail 7 | { 8 | public class GetSaleDetailQuery 9 | : IGetSaleDetailQuery 10 | { 11 | private readonly IDatabaseService _database; 12 | 13 | public GetSaleDetailQuery(IDatabaseService database) 14 | { 15 | _database = database; 16 | } 17 | 18 | public SaleDetailModel Execute(int saleId) 19 | { 20 | var sale = _database.Sales 21 | .Where(p => p.Id == saleId) 22 | .Select(p => new SaleDetailModel() 23 | { 24 | Id = p.Id, 25 | Date = p.Date, 26 | CustomerName = p.Customer.Name, 27 | EmployeeName = p.Employee.Name, 28 | ProductName = p.Product.Name, 29 | UnitPrice = p.UnitPrice, 30 | Quantity = p.Quantity, 31 | TotalPrice = p.TotalPrice 32 | }) 33 | .Single(); 34 | 35 | return sale; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Application/Sales/Queries/GetSaleDetail/GetSaleDetailQueryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore; 4 | using System.Linq; 5 | using Moq.AutoMock; 6 | using Moq.EntityFrameworkCore; 7 | using CleanArchitecture.Application.Interfaces; 8 | using CleanArchitecture.Domain.Customers; 9 | using CleanArchitecture.Domain.Employees; 10 | using CleanArchitecture.Domain.Products; 11 | using CleanArchitecture.Domain.Sales; 12 | using NUnit.Framework; 13 | 14 | namespace CleanArchitecture.Application.Sales.Queries.GetSaleDetail 15 | { 16 | [TestFixture] 17 | public class GetSaleDetailQueryTests 18 | { 19 | private GetSaleDetailQuery _query; 20 | private AutoMocker _mocker; 21 | private Sale _sale; 22 | 23 | private const int SaleId = 1; 24 | private static readonly DateTime Date = new DateTime(2001, 2, 3); 25 | private const string CustomerName = "Customer 1"; 26 | private const string EmployeeName = "Employee 1"; 27 | private const string ProductName = "Product 1"; 28 | private const decimal UnitPrice = 1.23m; 29 | private const int Quantity = 2; 30 | private const decimal TotalPrice = 2.46m; 31 | 32 | [SetUp] 33 | public void SetUp() 34 | { 35 | var customer = new Customer 36 | { 37 | Name = CustomerName 38 | }; 39 | 40 | var employee = new Employee 41 | { 42 | Name = EmployeeName 43 | }; 44 | 45 | var product = new Product 46 | { 47 | Name = ProductName 48 | }; 49 | 50 | _sale = new Sale() 51 | { 52 | Id = SaleId, 53 | Date = Date, 54 | Customer = customer, 55 | Employee = employee, 56 | Product = product, 57 | UnitPrice = UnitPrice, 58 | Quantity = Quantity 59 | }; 60 | 61 | _mocker = new AutoMocker(); 62 | 63 | _mocker.GetMock() 64 | .Setup(p => p.Sales) 65 | .ReturnsDbSet(new List { _sale }); 66 | 67 | _query = _mocker.CreateInstance(); 68 | } 69 | 70 | [Test] 71 | public void TestExecuteShouldReturnSaleDetail() 72 | { 73 | var result = _query.Execute(SaleId); 74 | 75 | Assert.That(result.Id, 76 | Is.EqualTo(SaleId)); 77 | 78 | Assert.That(result.Date, 79 | Is.EqualTo(Date)); 80 | 81 | Assert.That(result.CustomerName, 82 | Is.EqualTo(CustomerName)); 83 | 84 | Assert.That(result.EmployeeName, 85 | Is.EqualTo(EmployeeName)); 86 | 87 | Assert.That(result.ProductName, 88 | Is.EqualTo(ProductName)); 89 | 90 | Assert.That(result.UnitPrice, 91 | Is.EqualTo(UnitPrice)); 92 | 93 | Assert.That(result.Quantity, 94 | Is.EqualTo(Quantity)); 95 | 96 | Assert.That(result.TotalPrice, 97 | Is.EqualTo(TotalPrice)); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Application/Sales/Queries/GetSaleDetail/IGetSaleDetailQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Application.Sales.Queries.GetSaleDetail 6 | { 7 | public interface IGetSaleDetailQuery 8 | { 9 | SaleDetailModel Execute(int id); 10 | } 11 | } -------------------------------------------------------------------------------- /Application/Sales/Queries/GetSaleDetail/SaleDetailModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Application.Sales.Queries.GetSaleDetail 6 | { 7 | public class SaleDetailModel 8 | { 9 | public int Id { get; set; } 10 | 11 | public DateTime Date { get; set; } 12 | 13 | public string CustomerName { get; set; } 14 | 15 | public string EmployeeName { get; set; } 16 | 17 | public string ProductName { get; set; } 18 | 19 | public decimal UnitPrice { get; set; } 20 | 21 | public int Quantity { get; set; } 22 | 23 | public decimal TotalPrice { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /Application/Sales/Queries/GetSalesList/GetSalesListQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Interfaces; 5 | 6 | namespace CleanArchitecture.Application.Sales.Queries.GetSalesList 7 | { 8 | public class GetSalesListQuery 9 | : IGetSalesListQuery 10 | { 11 | private readonly IDatabaseService _database; 12 | 13 | public GetSalesListQuery(IDatabaseService database) 14 | { 15 | _database = database; 16 | } 17 | 18 | public List Execute() 19 | { 20 | var sales = _database.Sales 21 | .Select(p => new SalesListItemModel() 22 | { 23 | Id = p.Id, 24 | Date = p.Date, 25 | CustomerName = p.Customer.Name, 26 | EmployeeName = p.Employee.Name, 27 | ProductName = p.Product.Name, 28 | UnitPrice = p.UnitPrice, 29 | Quantity = p.Quantity, 30 | TotalPrice = p.TotalPrice 31 | }); 32 | 33 | return sales.ToList(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Application/Sales/Queries/GetSalesList/GetSalesListQueryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore; 4 | using System.Linq; 5 | using Moq.AutoMock; 6 | using Moq.EntityFrameworkCore; 7 | using CleanArchitecture.Application.Interfaces; 8 | using CleanArchitecture.Domain.Customers; 9 | using CleanArchitecture.Domain.Employees; 10 | using CleanArchitecture.Domain.Products; 11 | using CleanArchitecture.Domain.Sales; 12 | using NUnit.Framework; 13 | 14 | namespace CleanArchitecture.Application.Sales.Queries.GetSalesList 15 | { 16 | [TestFixture] 17 | public class GetSalesListQueryTests 18 | { 19 | private GetSalesListQuery _query; 20 | private AutoMocker _mocker; 21 | private Sale _sale; 22 | 23 | private const int SaleId = 1; 24 | private static readonly DateTime Date = new DateTime(2001, 2, 3); 25 | private const string CustomerName = "Customer 1"; 26 | private const string EmployeeName = "Employee 1"; 27 | private const string ProductName = "Product 1"; 28 | private const decimal UnitPrice = 1.23m; 29 | private const int Quantity = 2; 30 | private const decimal TotalPrice = 2.46m; 31 | 32 | [SetUp] 33 | public void SetUp() 34 | { 35 | var customer = new Customer 36 | { 37 | Name = CustomerName 38 | }; 39 | 40 | var employee = new Employee 41 | { 42 | Name = EmployeeName 43 | }; 44 | 45 | var product = new Product 46 | { 47 | Name = ProductName 48 | }; 49 | 50 | _sale = new Sale() 51 | { 52 | Id = SaleId, 53 | Date = Date, 54 | Customer = customer, 55 | Employee = employee, 56 | Product = product, 57 | UnitPrice = UnitPrice, 58 | Quantity = Quantity 59 | }; 60 | 61 | _mocker = new AutoMocker(); 62 | 63 | _mocker.GetMock() 64 | .Setup(p => p.Sales) 65 | .ReturnsDbSet(new List { _sale }); 66 | 67 | _query = _mocker.CreateInstance(); 68 | } 69 | 70 | [Test] 71 | public void TestExecuteShouldReturnListOfSales() 72 | { 73 | var results = _query.Execute(); 74 | 75 | var result = results.Single(); 76 | 77 | Assert.That(result.Id, 78 | Is.EqualTo(SaleId)); 79 | 80 | Assert.That(result.Date, 81 | Is.EqualTo(Date)); 82 | 83 | Assert.That(result.CustomerName, 84 | Is.EqualTo(CustomerName)); 85 | 86 | Assert.That(result.EmployeeName, 87 | Is.EqualTo(EmployeeName)); 88 | 89 | Assert.That(result.ProductName, 90 | Is.EqualTo(ProductName)); 91 | 92 | Assert.That(result.UnitPrice, 93 | Is.EqualTo(UnitPrice)); 94 | 95 | Assert.That(result.Quantity, 96 | Is.EqualTo(Quantity)); 97 | 98 | Assert.That(result.TotalPrice, 99 | Is.EqualTo(TotalPrice)); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Application/Sales/Queries/GetSalesList/IGetSalesListQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CleanArchitecture.Application.Sales.Queries.GetSalesList 4 | { 5 | public interface IGetSalesListQuery 6 | { 7 | List Execute(); 8 | } 9 | } -------------------------------------------------------------------------------- /Application/Sales/Queries/GetSalesList/SalesListItemModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CleanArchitecture.Application.Sales.Queries.GetSalesList 4 | { 5 | public class SalesListItemModel 6 | { 7 | public int Id { get; set; } 8 | 9 | public DateTime Date { get; set; } 10 | 11 | public string CustomerName { get; set; } 12 | 13 | public string EmployeeName { get; set; } 14 | 15 | public string ProductName { get; set; } 16 | 17 | public decimal UnitPrice { get; set; } 18 | 19 | public int Quantity { get; set; } 20 | 21 | public decimal TotalPrice { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CleanArchitecture.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32811.315 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "Domain\Domain.csproj", "{2FEEC595-A92A-42BD-8551-3F98F0C770D1}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{D2150780-800E-403C-9A2B-907FDECCEC79}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{DB553111-B945-4E5D-A24F-7E0CFB3BB9E4}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{4AB309F6-717F-4374-9F81-EF8306E7A094}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\Persistence.csproj", "{B9E470CE-2783-46AD-9E6A-2590CC9D493B}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service\Service.csproj", "{C0EE4FD2-FC9A-45AF-893B-9AFE18BE7E84}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Presentation", "Presentation\Presentation.csproj", "{23FFAC9A-29D8-48D9-AF19-19F0611F3AF1}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Specification", "Specification\Specification.csproj", "{AF2482AD-FFE8-4204-99B2-E2A50DAD35AE}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {2FEEC595-A92A-42BD-8551-3F98F0C770D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {2FEEC595-A92A-42BD-8551-3F98F0C770D1}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {2FEEC595-A92A-42BD-8551-3F98F0C770D1}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {2FEEC595-A92A-42BD-8551-3F98F0C770D1}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {D2150780-800E-403C-9A2B-907FDECCEC79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {D2150780-800E-403C-9A2B-907FDECCEC79}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {D2150780-800E-403C-9A2B-907FDECCEC79}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {D2150780-800E-403C-9A2B-907FDECCEC79}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {DB553111-B945-4E5D-A24F-7E0CFB3BB9E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {DB553111-B945-4E5D-A24F-7E0CFB3BB9E4}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {DB553111-B945-4E5D-A24F-7E0CFB3BB9E4}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {DB553111-B945-4E5D-A24F-7E0CFB3BB9E4}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {4AB309F6-717F-4374-9F81-EF8306E7A094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {4AB309F6-717F-4374-9F81-EF8306E7A094}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {4AB309F6-717F-4374-9F81-EF8306E7A094}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {4AB309F6-717F-4374-9F81-EF8306E7A094}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {B9E470CE-2783-46AD-9E6A-2590CC9D493B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {B9E470CE-2783-46AD-9E6A-2590CC9D493B}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {B9E470CE-2783-46AD-9E6A-2590CC9D493B}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {B9E470CE-2783-46AD-9E6A-2590CC9D493B}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {C0EE4FD2-FC9A-45AF-893B-9AFE18BE7E84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {C0EE4FD2-FC9A-45AF-893B-9AFE18BE7E84}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {C0EE4FD2-FC9A-45AF-893B-9AFE18BE7E84}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {C0EE4FD2-FC9A-45AF-893B-9AFE18BE7E84}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {23FFAC9A-29D8-48D9-AF19-19F0611F3AF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {23FFAC9A-29D8-48D9-AF19-19F0611F3AF1}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {23FFAC9A-29D8-48D9-AF19-19F0611F3AF1}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {23FFAC9A-29D8-48D9-AF19-19F0611F3AF1}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {AF2482AD-FFE8-4204-99B2-E2A50DAD35AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {AF2482AD-FFE8-4204-99B2-E2A50DAD35AE}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {AF2482AD-FFE8-4204-99B2-E2A50DAD35AE}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {AF2482AD-FFE8-4204-99B2-E2A50DAD35AE}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(ExtensibilityGlobals) = postSolution 65 | SolutionGuid = {F1C404E8-DB29-4A8F-B21C-FFAB2FB48E25} 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /Common/Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | disable 7 | CleanArchitecture.$(MSBuildProjectName) 8 | CleanArchitecture.$(MSBuildProjectName.Replace(" ", "_")) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Common/Dates/DateService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Common.Dates 6 | { 7 | public class DateService : IDateService 8 | { 9 | public DateTime GetDate() 10 | { 11 | return DateTime.Now.Date; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Common/Dates/IDateService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Common.Dates 6 | { 7 | public interface IDateService 8 | { 9 | DateTime GetDate(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Diagrams/Commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardalis/clean-architecture-core/340cdf90b5a5a4b6c1c3879980361af3a23ffa7f/Diagrams/Commands.png -------------------------------------------------------------------------------- /Diagrams/Domain.dgml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 109 | 114 | 118 | 122 | 126 | 130 | 134 | 138 | 142 | 148 | 154 | 160 | 166 | 173 | 179 | 185 | 191 | 198 | 204 | 210 | 214 | 218 | 222 | 227 | 233 | 239 | 245 | 251 | 257 | 263 | 267 | 271 | 275 | 279 | 283 | 287 | 291 | 295 | 298 | 301 | 304 | 308 | 314 | 320 | 321 | 322 | 323 | 324 | 325 | -------------------------------------------------------------------------------- /Diagrams/Domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardalis/clean-architecture-core/340cdf90b5a5a4b6c1c3879980361af3a23ffa7f/Diagrams/Domain.png -------------------------------------------------------------------------------- /Diagrams/Microservices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardalis/clean-architecture-core/340cdf90b5a5a4b6c1c3879980361af3a23ffa7f/Diagrams/Microservices.png -------------------------------------------------------------------------------- /Diagrams/Queries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardalis/clean-architecture-core/340cdf90b5a5a4b6c1c3879980361af3a23ffa7f/Diagrams/Queries.png -------------------------------------------------------------------------------- /Diagrams/Solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardalis/clean-architecture-core/340cdf90b5a5a4b6c1c3879980361af3a23ffa7f/Diagrams/Solution.png -------------------------------------------------------------------------------- /Diagrams/Tests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardalis/clean-architecture-core/340cdf90b5a5a4b6c1c3879980361af3a23ffa7f/Diagrams/Tests.png -------------------------------------------------------------------------------- /Domain/Common/IEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Domain.Common 6 | { 7 | public interface IEntity 8 | { 9 | int Id { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Domain/Customers/Customer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CleanArchitecture.Domain.Common; 3 | 4 | namespace CleanArchitecture.Domain.Customers 5 | { 6 | public class Customer : IEntity 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Name { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Domain/Customers/CustomerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using NUnit.Framework; 7 | 8 | namespace CleanArchitecture.Domain.Customers 9 | { 10 | [TestFixture] 11 | public class CustomerTests 12 | { 13 | private Customer _customer; 14 | private const int Id = 1; 15 | private const string Name = "Test"; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _customer = new Customer(); 21 | } 22 | 23 | [Test] 24 | public void TestSetAndGetId() 25 | { 26 | _customer.Id = Id; 27 | 28 | Assert.That(_customer.Id, 29 | Is.EqualTo(Id)); 30 | } 31 | 32 | [Test] 33 | public void TestSetAndGetName() 34 | { 35 | _customer.Name = Name; 36 | 37 | Assert.That(_customer.Name, 38 | Is.EqualTo(Name)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Domain/Domain.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | disable 7 | CleanArchitecture.$(MSBuildProjectName) 8 | CleanArchitecture.$(MSBuildProjectName.Replace(" ", "_")) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Domain/Employees/Employee.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CleanArchitecture.Domain.Common; 3 | 4 | namespace CleanArchitecture.Domain.Employees 5 | { 6 | public class Employee : IEntity 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Name { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Domain/Employees/EmployeeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using NUnit.Framework; 7 | 8 | namespace CleanArchitecture.Domain.Employees 9 | { 10 | [TestFixture] 11 | public class EmployeeTests 12 | { 13 | private Employee _employee; 14 | private const int Id = 1; 15 | private const string Name = "Test"; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _employee = new Employee(); 21 | } 22 | 23 | [Test] 24 | public void TestSetAndGetId() 25 | { 26 | _employee.Id = Id; 27 | 28 | Assert.That(_employee.Id, 29 | Is.EqualTo(Id)); 30 | } 31 | 32 | [Test] 33 | public void TestSetAndGetName() 34 | { 35 | _employee.Name = Name; 36 | 37 | Assert.That(_employee.Name, 38 | Is.EqualTo(Name)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Domain/Products/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CleanArchitecture.Domain.Common; 3 | 4 | namespace CleanArchitecture.Domain.Products 5 | { 6 | public class Product : IEntity 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Name { get; set; } 11 | 12 | public decimal Price { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Domain/Products/ProductTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using NUnit.Framework; 7 | 8 | namespace CleanArchitecture.Domain.Products 9 | { 10 | [TestFixture] 11 | public class ProductTests 12 | { 13 | private Product _product; 14 | private const int Id = 1; 15 | private const string Name = "Test"; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _product = new Product(); 21 | } 22 | 23 | [Test] 24 | public void TestSetAndGetId() 25 | { 26 | _product.Id = Id; 27 | 28 | Assert.That(_product.Id, 29 | Is.EqualTo(Id)); 30 | } 31 | 32 | [Test] 33 | public void TestSetAndGetName() 34 | { 35 | _product.Name = Name; 36 | 37 | Assert.That(_product.Name, 38 | Is.EqualTo(Name)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Domain/Sales/Sale.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CleanArchitecture.Domain.Common; 4 | using CleanArchitecture.Domain.Customers; 5 | using CleanArchitecture.Domain.Employees; 6 | using CleanArchitecture.Domain.Products; 7 | 8 | namespace CleanArchitecture.Domain.Sales 9 | { 10 | public class Sale : IEntity 11 | { 12 | private int _quantity; 13 | private decimal _totalPrice; 14 | private decimal _unitPrice; 15 | 16 | public int Id { get; set; } 17 | 18 | public DateTime Date { get; set; } 19 | 20 | public Customer Customer { get; set; } 21 | 22 | public Employee Employee { get; set; } 23 | 24 | public Product Product { get; set; } 25 | 26 | public decimal UnitPrice 27 | { 28 | get { return _unitPrice; } 29 | set 30 | { 31 | _unitPrice = value; 32 | 33 | UpdateTotalPrice(); 34 | } 35 | } 36 | 37 | public int Quantity 38 | { 39 | get { return _quantity; } 40 | set 41 | { 42 | _quantity = value; 43 | 44 | UpdateTotalPrice(); 45 | } 46 | } 47 | 48 | public decimal TotalPrice 49 | { 50 | get { return _totalPrice; } 51 | private set { _totalPrice = value; } 52 | } 53 | 54 | private void UpdateTotalPrice() 55 | { 56 | _totalPrice = _unitPrice * _quantity; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Domain/Sales/SaleTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Domain.Customers; 5 | using CleanArchitecture.Domain.Employees; 6 | using CleanArchitecture.Domain.Products; 7 | using NUnit.Framework; 8 | 9 | namespace CleanArchitecture.Domain.Sales 10 | { 11 | [TestFixture] 12 | public class SaleTests 13 | { 14 | private Sale _sale; 15 | private Customer _customer; 16 | private Employee _employee; 17 | private Product _product; 18 | 19 | private const int Id = 1; 20 | private static readonly DateTime Date = new(2001, 2, 3); 21 | private const decimal UnitPrice = 1.00m; 22 | private const int Quantity = 1; 23 | 24 | 25 | [SetUp] 26 | public void SetUp() 27 | { 28 | _customer = new Customer(); 29 | 30 | _employee = new Employee(); 31 | 32 | _product = new Product(); 33 | 34 | _sale = new Sale(); 35 | } 36 | 37 | [Test] 38 | public void TestSetAndGetId() 39 | { 40 | _sale.Id = Id; 41 | 42 | Assert.That(_sale.Id, 43 | Is.EqualTo(Id)); 44 | } 45 | 46 | [Test] 47 | public void TestSetAndGetDate() 48 | { 49 | _sale.Date = Date; 50 | 51 | Assert.That(_sale.Date, 52 | Is.EqualTo(Date)); 53 | } 54 | 55 | [Test] 56 | public void TestSetAndGetCustomer() 57 | { 58 | _sale.Customer = _customer; 59 | 60 | Assert.That(_sale.Customer, 61 | Is.EqualTo(_customer)); 62 | } 63 | 64 | [Test] 65 | public void TestSetAndGetEmployee() 66 | { 67 | _sale.Employee = _employee; 68 | 69 | Assert.That(_sale.Employee, 70 | Is.EqualTo(_employee)); 71 | } 72 | 73 | [Test] 74 | public void TestSetAndGetProduct() 75 | { 76 | _sale.Product = _product; 77 | 78 | Assert.That(_sale.Product, 79 | Is.EqualTo(_product)); 80 | } 81 | 82 | [Test] 83 | public void TestSetAndGetUnitPrice() 84 | { 85 | _sale.UnitPrice = UnitPrice; 86 | 87 | Assert.That(_sale.UnitPrice, 88 | Is.EqualTo(UnitPrice)); 89 | } 90 | 91 | [Test] 92 | public void TestSetAndGetQuantity() 93 | { 94 | _sale.Quantity = Quantity; 95 | 96 | Assert.That(_sale.Quantity, 97 | Is.EqualTo(Quantity)); 98 | } 99 | 100 | [Test] 101 | public void TestSetUnitPriceShouldRecomputeTotalPrice() 102 | { 103 | _sale.Quantity = Quantity; 104 | 105 | _sale.UnitPrice = 1.23m; 106 | 107 | Assert.That(_sale.TotalPrice, 108 | Is.EqualTo(1.23m)); 109 | } 110 | 111 | [Test] 112 | public void TestSetQuantityShouldRecomputeTotalPrice() 113 | { 114 | _sale.UnitPrice = UnitPrice; 115 | 116 | _sale.Quantity = 2; 117 | 118 | Assert.That(_sale.TotalPrice, 119 | Is.EqualTo(2.00m)); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Infrastructure/Infrastructure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | disable 7 | CleanArchitecture.$(MSBuildProjectName) 8 | CleanArchitecture.$(MSBuildProjectName.Replace(" ", "_")) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Infrastructure/Inventory/InventoryService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Interfaces; 5 | using CleanArchitecture.Infrastructure.Network; 6 | 7 | namespace CleanArchitecture.Infrastructure.Inventory 8 | { 9 | public class InventoryService 10 | : IInventoryService 11 | { 12 | // Note: these are hard coded to keep the demo simple 13 | private const string AddressTemplate = "http://abc123.com/inventory/products/{0}/notifysaleoccured/"; 14 | private const string JsonTemplate = "{{\"quantity\": {0}}}"; 15 | 16 | private readonly IHttpClientWrapper _client; 17 | 18 | public InventoryService(IHttpClientWrapper client) 19 | { 20 | _client = client; 21 | } 22 | 23 | public void NotifySaleOccurred(int productId, int quantity) 24 | { 25 | var address = string.Format(AddressTemplate, productId); 26 | 27 | var json = string.Format(JsonTemplate, quantity); 28 | 29 | _client.Post(address, json); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Infrastructure/Inventory/InventoryServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Infrastructure.Network; 5 | using Moq; 6 | using Moq.AutoMock; 7 | using NUnit.Framework; 8 | 9 | namespace CleanArchitecture.Infrastructure.Inventory 10 | { 11 | [TestFixture] 12 | public class InventoryServiceTests 13 | { 14 | private InventoryService _service; 15 | private AutoMocker _mocker; 16 | 17 | private const string Address = "http://abc123.com/inventory/products/1/notifysaleoccured/"; 18 | private const string Json = "{\"quantity\": 2}"; 19 | 20 | [SetUp] 21 | public void SetUp() 22 | { 23 | _mocker = new AutoMocker(); 24 | 25 | _service = _mocker.CreateInstance(); 26 | } 27 | 28 | [Test] 29 | public void TestNotifySaleOccurredShouldNotifyInventorySystem() 30 | { 31 | _service.NotifySaleOccurred(1, 2); 32 | 33 | _mocker.GetMock() 34 | .Verify(p => p.Post(Address, Json), 35 | Times.Once); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Infrastructure/Network/HttpClientWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http.Json; 6 | 7 | namespace CleanArchitecture.Infrastructure.Network 8 | { 9 | public class HttpClientWrapper : IHttpClientWrapper 10 | { 11 | public void Post(string address, string json) 12 | { 13 | using var client = new HttpClient(); 14 | 15 | // Note: This next line is commented out to prevent an 16 | // Note: actual HTTP call, since this is just a demo app. 17 | 18 | // client.PostAsJsonAsync(address, json); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Infrastructure/Network/IHttpClientWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Infrastructure.Network 6 | { 7 | public interface IHttpClientWrapper 8 | { 9 | void Post(string address, string json); 10 | } 11 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) [year], [fullname] 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Persistence/Customers/CustomerConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Domain.Customers; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 7 | 8 | namespace CleanArchitecture.Persistence.Customers 9 | { 10 | public class CustomerConfiguration 11 | : IEntityTypeConfiguration 12 | { 13 | 14 | public void Configure(EntityTypeBuilder builder) 15 | { 16 | builder.HasKey(p => p.Id); 17 | 18 | builder.Property(p => p.Name) 19 | .IsRequired() 20 | .HasMaxLength(50); 21 | 22 | builder.HasData( 23 | new Customer() { Id = 1, Name = "Martin Fowler" }, 24 | new Customer() { Id = 2, Name = "Uncle Bob" }, 25 | new Customer() { Id = 3, Name = "Kent Beck" }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Persistence/DatabaseService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.Configuration; 5 | using System.Linq; 6 | using CleanArchitecture.Application.Interfaces; 7 | using CleanArchitecture.Domain.Customers; 8 | using CleanArchitecture.Domain.Employees; 9 | using CleanArchitecture.Domain.Products; 10 | using CleanArchitecture.Domain.Sales; 11 | using CleanArchitecture.Persistence.Customers; 12 | using CleanArchitecture.Persistence.Employees; 13 | using CleanArchitecture.Persistence.Products; 14 | using CleanArchitecture.Persistence.Sales; 15 | 16 | 17 | namespace CleanArchitecture.Persistence 18 | { 19 | public class DatabaseService : DbContext, IDatabaseService 20 | { 21 | private readonly IConfiguration _configuration; 22 | 23 | public DatabaseService(IConfiguration configuration) 24 | { 25 | _configuration = configuration; 26 | 27 | Database.EnsureCreated(); 28 | } 29 | 30 | public DbSet Customers { get; set; } 31 | 32 | public DbSet Employees { get; set; } 33 | 34 | public DbSet Products { get; set; } 35 | 36 | public DbSet Sales { get; set; } 37 | 38 | public void Save() 39 | { 40 | this.SaveChanges(); 41 | } 42 | 43 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 44 | { 45 | var connectionString = _configuration.GetConnectionString("CleanArchitectureCore"); 46 | 47 | optionsBuilder.UseSqlServer(connectionString); 48 | } 49 | 50 | protected override void OnModelCreating(ModelBuilder builder) 51 | { 52 | base.OnModelCreating(builder); 53 | 54 | new CustomerConfiguration().Configure(builder.Entity()); 55 | new EmployeeConfiguration().Configure(builder.Entity()); 56 | new ProductConfiguration().Configure(builder.Entity()); 57 | new SaleConfiguration().Configure(builder.Entity()); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Persistence/Employees/EmployeeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Domain.Employees; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 7 | 8 | namespace CleanArchitecture.Persistence.Employees 9 | { 10 | public class EmployeeConfiguration 11 | : IEntityTypeConfiguration 12 | { 13 | 14 | public void Configure(EntityTypeBuilder builder) 15 | { 16 | builder.HasKey(p => p.Id); 17 | 18 | builder.Property(p => p.Name) 19 | .IsRequired() 20 | .HasMaxLength(50); 21 | 22 | builder.HasData( 23 | new Employee() { Id = 1, Name = "Eric Evans" }, 24 | new Employee() { Id = 2, Name = "Greg Young" }, 25 | new Employee() { Id = 3, Name = "Udi Dahan" }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Persistence/Persistence.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | disable 7 | CleanArchitecture.$(MSBuildProjectName) 8 | CleanArchitecture.$(MSBuildProjectName.Replace(" ", "_")) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Persistence/Products/ProductConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Domain.Products; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 7 | 8 | namespace CleanArchitecture.Persistence.Products 9 | { 10 | public class ProductConfiguration 11 | : IEntityTypeConfiguration 12 | { 13 | 14 | public void Configure(EntityTypeBuilder builder) 15 | { 16 | builder.HasKey(p => p.Id); 17 | 18 | builder.Property(p => p.Name) 19 | .IsRequired() 20 | .HasMaxLength(50); 21 | 22 | builder.Property(p => p.Price) 23 | .IsRequired() 24 | .HasPrecision(5, 2); 25 | 26 | builder.HasData( 27 | new Product() { Id = 1, Name = "Spaghetti", Price = 5m }, 28 | new Product() { Id = 2, Name = "Lasagne", Price = 10m }, 29 | new Product() { Id = 3, Name = "Ravioli", Price = 15m }); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Persistence/Sales/SaleConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Domain.Sales; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 7 | 8 | namespace CleanArchitecture.Persistence.Sales 9 | { 10 | public class SaleConfiguration 11 | : IEntityTypeConfiguration 12 | { 13 | public void Configure(EntityTypeBuilder builder) 14 | { 15 | builder.HasKey(p => p.Id); 16 | 17 | builder.Property(p => p.Date) 18 | .IsRequired(); 19 | 20 | builder.HasOne(p => p.Customer); 21 | 22 | builder.Navigation(p => p.Customer) 23 | .IsRequired() 24 | .AutoInclude(); 25 | 26 | builder.HasOne(p => p.Employee); 27 | 28 | builder.Navigation(p => p.Employee) 29 | .IsRequired() 30 | .AutoInclude(); 31 | 32 | builder.HasOne(p => p.Product); 33 | 34 | builder.Navigation(p => p.Product) 35 | .IsRequired() 36 | .AutoInclude(); 37 | 38 | builder.Property(p => p.TotalPrice) 39 | .IsRequired() 40 | .HasPrecision(5, 2); 41 | 42 | // Note: Uses anonomous types to seed foreign keys 43 | builder.HasData( 44 | new 45 | { 46 | Id = 1, 47 | Date = DateTime.Parse("2022-01-01"), 48 | CustomerId = 1, 49 | EmployeeId = 1, 50 | ProductId = 1, 51 | UnitPrice = 5m, 52 | Quantity = 1, 53 | TotalPrice = 5m 54 | }, 55 | new 56 | { 57 | Id = 2, 58 | Date = DateTime.Parse("2022-01-02"), 59 | CustomerId = 2, 60 | EmployeeId = 2, 61 | ProductId = 2, 62 | UnitPrice = 10m, 63 | Quantity = 2, 64 | TotalPrice = 20m 65 | }, 66 | new 67 | { 68 | Id = 3, 69 | Date = DateTime.Parse("2022-01-03"), 70 | CustomerId = 3, 71 | EmployeeId = 3, 72 | ProductId = 3, 73 | UnitPrice = 15m, 74 | Quantity = 3, 75 | TotalPrice = 45m 76 | }); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Presentation/Content/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Set padding to keep content from hitting the edges */ 7 | .body-content { 8 | padding-left: 15px; 9 | padding-right: 15px; 10 | } 11 | 12 | /* Override the default bootstrap behavior where horizontal description lists 13 | will truncate terms that are too long to fit in the left column 14 | */ 15 | .dl-horizontal dt { 16 | white-space: normal; 17 | } 18 | 19 | /* Set width on the form input elements since they're 100% wide by default */ 20 | input, 21 | select, 22 | textarea { 23 | max-width: 280px; 24 | } 25 | -------------------------------------------------------------------------------- /Presentation/CustomViewLocationExpander.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc.Razor; 3 | 4 | namespace CleanArchitecture.Presentation 5 | { 6 | public class CustomViewLocationExpander : IViewLocationExpander 7 | { 8 | public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) 9 | { 10 | return new[] 11 | { 12 | "~/{1}/Views/{0}.cshtml", 13 | "~/Shared/Views/{0}.cshtml" 14 | }; 15 | } 16 | 17 | public void PopulateValues(ViewLocationExpanderContext context) 18 | { 19 | // Nothing needs to be done here 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Presentation/Customers/CustomersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | using CleanArchitecture.Application.Customers.Queries.GetCustomerList; 4 | 5 | namespace CleanArchitecture.Presentation.Customers 6 | { 7 | public class CustomersController : Controller 8 | { 9 | private readonly IGetCustomersListQuery _query; 10 | 11 | public CustomersController(IGetCustomersListQuery query) 12 | { 13 | _query = query; 14 | } 15 | 16 | public ViewResult Index() 17 | { 18 | var customers = _query.Execute(); 19 | 20 | return View(customers); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Presentation/Customers/CustomersControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moq.AutoMock; 5 | using CleanArchitecture.Application.Customers.Queries.GetCustomerList; 6 | using NUnit.Framework; 7 | 8 | namespace CleanArchitecture.Presentation.Customers 9 | { 10 | [TestFixture] 11 | public class CustomersControllerTests 12 | { 13 | private CustomersController _controller; 14 | private AutoMocker _mocker; 15 | private CustomerModel _model; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _model = new CustomerModel(); 21 | 22 | _mocker = new AutoMocker(); 23 | 24 | _mocker.GetMock() 25 | .Setup(p => p.Execute()) 26 | .Returns(new List { _model }); 27 | 28 | _controller = _mocker.CreateInstance(); 29 | } 30 | 31 | [Test] 32 | public void TestGetIndexShouldReturnListOfCustomers() 33 | { 34 | var viewResult = _controller.Index(); 35 | 36 | var result = (List) viewResult.Model; 37 | 38 | Assert.That(result.Single(), Is.EqualTo(_model)); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Presentation/Customers/Views/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 | @{ 4 | ViewBag.Title = "Customers"; 5 | } 6 | 7 |

Customers

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @foreach (var customer in Model) 17 | { 18 | 19 | 20 | 21 | 22 | } 23 | 24 |
IDName
@customer.Id@customer.Name
25 | 26 | -------------------------------------------------------------------------------- /Presentation/Employees/EmployeesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | using CleanArchitecture.Application.Employees.Queries.GetEmployeesList; 4 | 5 | namespace CleanArchitecture.Presentation.Employees 6 | { 7 | public class EmployeesController : Controller 8 | { 9 | private readonly IGetEmployeesListQuery _query; 10 | 11 | public EmployeesController(IGetEmployeesListQuery query) 12 | { 13 | _query = query; 14 | } 15 | 16 | public ViewResult Index() 17 | { 18 | var employees = _query.Execute(); 19 | 20 | return View(employees); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Presentation/Employees/EmployeesControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moq.AutoMock; 5 | using CleanArchitecture.Application.Employees.Queries.GetEmployeesList; 6 | using NUnit.Framework; 7 | 8 | namespace CleanArchitecture.Presentation.Employees 9 | { 10 | [TestFixture] 11 | public class EmployeesControllerTests 12 | { 13 | private EmployeesController _controller; 14 | private AutoMocker _mocker; 15 | private EmployeeModel _model; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _model = new EmployeeModel(); 21 | 22 | _mocker = new AutoMocker(); 23 | 24 | _mocker.GetMock() 25 | .Setup(p => p.Execute()) 26 | .Returns(new List { _model }); 27 | 28 | _controller = _mocker.CreateInstance(); 29 | } 30 | 31 | [Test] 32 | public void TestGetIndexShouldReturnListOfEmployees() 33 | { 34 | var viewResult = _controller.Index(); 35 | 36 | var result = (List) viewResult.Model; 37 | 38 | Assert.That(result.Single(), Is.EqualTo(_model)); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Presentation/Employees/Views/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Employees"; 3 | } 4 | 5 |

Employees

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | @foreach (var employee in Model) 15 | { 16 | 17 | 18 | 19 | 20 | } 21 | 22 |
IDName
@employee.Id@employee.Name
23 | 24 | -------------------------------------------------------------------------------- /Presentation/Home/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace CleanArchitecture.Presentation.Home 6 | { 7 | public class HomeController : Controller 8 | { 9 | public ViewResult Index() 10 | { 11 | return View(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Presentation/Home/HomeControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Moq.AutoMock; 6 | using NUnit.Framework; 7 | 8 | namespace CleanArchitecture.Presentation.Home 9 | { 10 | [TestFixture] 11 | public class HomeControllerTests 12 | { 13 | private HomeController _controller; 14 | private AutoMocker _mocker; 15 | 16 | [SetUp] 17 | public void SetUp() 18 | { 19 | _mocker = new AutoMocker(); 20 | 21 | _controller = _mocker.CreateInstance(); 22 | } 23 | 24 | [Test] 25 | public void TestGetIndexShouldReturnView() 26 | { 27 | var result = _controller.Index(); 28 | 29 | Assert.That(result, Is.TypeOf()); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Presentation/Home/Views/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Home"; 3 | } 4 | 5 |

Home

6 | 7 |

Welcome to the Clean Architecture sample application! : )

8 | 9 |

Please visit one of the following pages:

10 | 11 |
    12 |
  • @Html.ActionLink("Customers", "Index", "customers")
  • 13 |
  • @Html.ActionLink("Employees", "Index", "employees")
  • 14 |
  • @Html.ActionLink("Products", "index", "products")
  • 15 |
  • @Html.ActionLink("Sales", "Index", "sales")
  • 16 |
17 | 18 | -------------------------------------------------------------------------------- /Presentation/Presentation.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | disable 6 | enable 7 | CleanArchitecture.Presentation.Program 8 | CleanArchitecture.$(MSBuildProjectName) 9 | CleanArchitecture.$(MSBuildProjectName.Replace(" ", "_")) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | true 34 | PreserveNewest 35 | 36 | 37 | true 38 | PreserveNewest 39 | 40 | 41 | true 42 | PreserveNewest 43 | 44 | 45 | true 46 | PreserveNewest 47 | 48 | 49 | true 50 | PreserveNewest 51 | 52 | 53 | true 54 | PreserveNewest 55 | 56 | 57 | true 58 | PreserveNewest 59 | 60 | 61 | true 62 | PreserveNewest 63 | 64 | 65 | true 66 | PreserveNewest 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Presentation/Products/ProductsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using CleanArchitecture.Application.Products.Queries.GetProductsList; 6 | 7 | namespace CleanArchitecture.Presentation.Products 8 | { 9 | public class ProductsController : Controller 10 | { 11 | private readonly IGetProductsListQuery _query; 12 | 13 | public ProductsController(IGetProductsListQuery query) 14 | { 15 | _query = query; 16 | } 17 | 18 | public ViewResult Index() 19 | { 20 | var products = _query.Execute(); 21 | 22 | return View(products); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Presentation/Products/ProductsControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moq.AutoMock; 5 | using CleanArchitecture.Application.Products.Queries.GetProductsList; 6 | using NUnit.Framework; 7 | 8 | namespace CleanArchitecture.Presentation.Products 9 | { 10 | [TestFixture] 11 | public class ProductsControllerTests 12 | { 13 | private ProductsController _controller; 14 | private AutoMocker _mocker; 15 | private ProductModel _model; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _model = new ProductModel(); 21 | 22 | _mocker = new AutoMocker(); 23 | 24 | _mocker.GetMock() 25 | .Setup(p => p.Execute()) 26 | .Returns(new List { _model }); 27 | 28 | _controller = _mocker.CreateInstance(); 29 | } 30 | 31 | [Test] 32 | public void TestGetIndexShouldReturnListOfProducts() 33 | { 34 | var viewResult = _controller.Index(); 35 | 36 | var result = (List)viewResult.Model; 37 | 38 | Assert.That(result.Single(), Is.EqualTo(_model)); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Presentation/Products/Views/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 | @{ 4 | ViewBag.Title = "Products"; 5 | } 6 | 7 |

Products

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | @foreach (var products in Model) 18 | { 19 | 20 | 21 | 22 | 23 | 24 | } 25 | 26 |
IDNamePrice
@products.Id@products.Name$@products.UnitPrice
-------------------------------------------------------------------------------- /Presentation/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Loader; 2 | using Microsoft.AspNetCore.Mvc.Razor; 3 | using Microsoft.Extensions.FileProviders; 4 | 5 | namespace CleanArchitecture.Presentation; 6 | 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | var files = Directory.GetFiles( 12 | AppDomain.CurrentDomain.BaseDirectory, 13 | "CleanArchitecture*.dll"); 14 | 15 | var assemblies = files 16 | .Select(p => AssemblyLoadContext.Default.LoadFromAssemblyPath(p)); 17 | 18 | var builder = WebApplication.CreateBuilder(args); 19 | 20 | builder.Services.AddControllersWithViews(); 21 | 22 | builder.Services.Configure( 23 | p => p.ViewLocationExpanders.Add( 24 | new CustomViewLocationExpander())); 25 | 26 | builder.Services.Scan(p => p.FromAssemblies(assemblies) 27 | .AddClasses() 28 | .AsMatchingInterface()); 29 | 30 | var app = builder.Build(); 31 | 32 | if (!app.Environment.IsDevelopment()) 33 | { 34 | app.UseExceptionHandler("/Home/Error"); 35 | 36 | app.UseHsts(); 37 | } 38 | 39 | app.UseHttpsRedirection(); 40 | 41 | app.UseStaticFiles( 42 | new StaticFileOptions 43 | { 44 | FileProvider = new PhysicalFileProvider( 45 | Path.Combine( 46 | Directory.GetCurrentDirectory(), 47 | "Content")), 48 | RequestPath = "/content" 49 | }); 50 | 51 | app.UseRouting(); 52 | 53 | app.UseAuthorization(); 54 | 55 | app.MapControllerRoute( 56 | name: "default", 57 | pattern: "{controller=Home}/{action=Index}/{id?}"); 58 | 59 | app.UseAdvancedDependencyInjection(); 60 | 61 | app.Run(); 62 | } 63 | } -------------------------------------------------------------------------------- /Presentation/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:21522", 7 | "sslPort": 44331 8 | } 9 | }, 10 | "profiles": { 11 | "Presentation": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "launchUrl": "https://localhost:7095/home/", 16 | "applicationUrl": "https://localhost:7095;http://localhost:5095", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Presentation/Sales/Models/CreateSaleViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc; 3 | using CleanArchitecture.Application.Sales.Commands.CreateSale; 4 | using Microsoft.AspNetCore.Mvc.Rendering; 5 | 6 | namespace CleanArchitecture.Presentation.Sales.Models 7 | { 8 | public class CreateSaleViewModel 9 | { 10 | public List Customers { get; set; } 11 | 12 | public List Employees { get; set; } 13 | 14 | public List Products { get; set; } 15 | 16 | public CreateSaleModel Sale { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Presentation/Sales/SalesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Mvc; 4 | using CleanArchitecture.Application.Sales.Commands.CreateSale; 5 | using CleanArchitecture.Application.Sales.Queries.GetSaleDetail; 6 | using CleanArchitecture.Application.Sales.Queries.GetSalesList; 7 | using CleanArchitecture.Presentation.Sales.Models; 8 | using CleanArchitecture.Presentation.Sales.Services; 9 | 10 | namespace CleanArchitecture.Presentation.Sales 11 | { 12 | [Route("sales")] 13 | public class SalesController : Controller 14 | { 15 | private readonly IGetSalesListQuery _salesListQuery; 16 | private readonly IGetSaleDetailQuery _saleDetailQuery; 17 | private readonly ICreateSaleViewModelFactory _factory; 18 | private readonly ICreateSaleCommand _createCommand; 19 | 20 | public SalesController( 21 | IGetSalesListQuery salesListQuery, 22 | IGetSaleDetailQuery saleDetailQuery, 23 | ICreateSaleViewModelFactory factory, 24 | ICreateSaleCommand createCommand) 25 | { 26 | _salesListQuery = salesListQuery; 27 | _saleDetailQuery = saleDetailQuery; 28 | _factory = factory; 29 | _createCommand = createCommand; 30 | } 31 | 32 | [Route("")] 33 | public ViewResult Index() 34 | { 35 | var sales = _salesListQuery.Execute(); 36 | 37 | return View(sales); 38 | } 39 | 40 | [Route("{id:int}")] 41 | public ViewResult Detail(int id) 42 | { 43 | var sale = _saleDetailQuery.Execute(id); 44 | 45 | return View(sale); 46 | } 47 | 48 | [Route("create")] 49 | public ViewResult Create() 50 | { 51 | var viewModel = _factory.Create(); 52 | 53 | return View(viewModel); 54 | } 55 | 56 | [Route("create")] 57 | [HttpPost] 58 | public IActionResult Create(CreateSaleViewModel viewModel) 59 | { 60 | var model = viewModel.Sale; 61 | 62 | _createCommand.Execute(model); 63 | 64 | return RedirectToAction("index", "sales"); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Presentation/Sales/SalesControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moq.AutoMock; 5 | using CleanArchitecture.Application.Sales.Commands.CreateSale; 6 | using CleanArchitecture.Application.Sales.Queries.GetSaleDetail; 7 | using CleanArchitecture.Application.Sales.Queries.GetSalesList; 8 | using CleanArchitecture.Presentation.Sales.Models; 9 | using CleanArchitecture.Presentation.Sales.Services; 10 | using NUnit.Framework; 11 | 12 | namespace CleanArchitecture.Presentation.Sales 13 | { 14 | [TestFixture] 15 | public class SalesControllerTests 16 | { 17 | private SalesController _controller; 18 | private AutoMocker _mocker; 19 | 20 | [SetUp] 21 | public void SetUp() 22 | { 23 | _mocker = new AutoMocker(); 24 | 25 | _controller = _mocker.CreateInstance(); 26 | } 27 | 28 | [Test] 29 | public void TestGetIndexShouldReturnListOfSales() 30 | { 31 | var model = new SalesListItemModel(); 32 | 33 | _mocker.GetMock() 34 | .Setup(p => p.Execute()) 35 | .Returns(new List { model }); 36 | 37 | var viewResult = _controller.Index(); 38 | 39 | var results = (List) viewResult.Model; 40 | 41 | Assert.That(results.Single(), Is.EqualTo(model)); 42 | } 43 | 44 | [Test] 45 | public void TestGetDetailShouldReturnSaleDetail() 46 | { 47 | var saleId = 1; 48 | 49 | var model = new SaleDetailModel(); 50 | 51 | _mocker.GetMock() 52 | .Setup(p => p.Execute(saleId)) 53 | .Returns(model); 54 | 55 | var viewResult = _controller.Detail(saleId); 56 | 57 | var result = (SaleDetailModel) viewResult.Model; 58 | 59 | Assert.That(result, Is.EqualTo(model)); 60 | } 61 | 62 | [Test] 63 | public void TestGetCreateShouldReturnCreateSaleViewModel() 64 | { 65 | var viewModel = new CreateSaleViewModel(); 66 | 67 | _mocker.GetMock() 68 | .Setup(p => p.Create()) 69 | .Returns(viewModel); 70 | 71 | var viewResult = _controller.Create(); 72 | 73 | var result = (CreateSaleViewModel) viewResult.Model; 74 | 75 | Assert.That(result, Is.EqualTo(viewModel)); 76 | } 77 | 78 | [Test] 79 | public void TestPostCreateShouldReturnExecuteCreateSaleCommand() 80 | { 81 | var model = new CreateSaleModel(); 82 | 83 | var viewModel = new CreateSaleViewModel() 84 | { 85 | Sale = model 86 | }; 87 | 88 | _controller.Create(viewModel); 89 | 90 | _mocker.GetMock() 91 | .Verify(p => p.Execute(model)); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /Presentation/Sales/Services/CreateSaleViewModelFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using CleanArchitecture.Application.Customers.Queries.GetCustomerList; 6 | using CleanArchitecture.Application.Employees.Queries.GetEmployeesList; 7 | using CleanArchitecture.Application.Products.Queries.GetProductsList; 8 | using CleanArchitecture.Application.Sales.Commands.CreateSale; 9 | using CleanArchitecture.Presentation.Sales.Models; 10 | using Microsoft.AspNetCore.Mvc.Rendering; 11 | 12 | namespace CleanArchitecture.Presentation.Sales.Services 13 | { 14 | public class CreateSaleViewModelFactory : ICreateSaleViewModelFactory 15 | { 16 | private readonly IGetCustomersListQuery _customersQuery; 17 | private readonly IGetEmployeesListQuery _employeesQuery; 18 | private readonly IGetProductsListQuery _productsQuery; 19 | 20 | public CreateSaleViewModelFactory( 21 | IGetCustomersListQuery customersQuery, 22 | IGetEmployeesListQuery employeesQuery, 23 | IGetProductsListQuery productsQuery) 24 | { 25 | _customersQuery = customersQuery; 26 | _employeesQuery = employeesQuery; 27 | _productsQuery = productsQuery; 28 | } 29 | 30 | public CreateSaleViewModel Create() 31 | { 32 | var employees = _employeesQuery.Execute(); 33 | 34 | var customers = _customersQuery.Execute(); 35 | 36 | var products = _productsQuery.Execute(); 37 | 38 | var viewModel = new CreateSaleViewModel(); 39 | 40 | viewModel.Employees = employees 41 | .Select(p => new SelectListItem() 42 | { 43 | Value = p.Id.ToString(), 44 | Text = p.Name 45 | }) 46 | .ToList(); 47 | 48 | viewModel.Customers = customers 49 | .Select(p => new SelectListItem() 50 | { 51 | Value = p.Id.ToString(), 52 | Text = p.Name 53 | }) 54 | .ToList(); 55 | 56 | viewModel.Products = products 57 | .Select(p => new SelectListItem() 58 | { 59 | Value = p.Id.ToString(), 60 | Text = p.Name + " ($" + p.UnitPrice + ")" 61 | }) 62 | .ToList(); 63 | 64 | viewModel.Sale = new CreateSaleModel(); 65 | 66 | return viewModel; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Presentation/Sales/Services/CreateSaleViewModelFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moq.AutoMock; 5 | using CleanArchitecture.Application.Customers.Queries.GetCustomerList; 6 | using CleanArchitecture.Application.Employees.Queries.GetEmployeesList; 7 | using CleanArchitecture.Application.Products.Queries.GetProductsList; 8 | using NUnit.Framework; 9 | 10 | namespace CleanArchitecture.Presentation.Sales.Services 11 | { 12 | [TestFixture] 13 | public class CreateSaleViewModelFactoryTests 14 | { 15 | private CreateSaleViewModelFactory _factory; 16 | private AutoMocker _mocker; 17 | 18 | private const int CustomerId = 1; 19 | private const string CustomerName = "Customer 1"; 20 | private const int EmployeeId = 2; 21 | private const string EmployeeName = "Employee 2"; 22 | private const int ProductId = 3; 23 | private const string ProductName = "Product 3"; 24 | private const decimal ProductPrice = 1.23m; 25 | 26 | [SetUp] 27 | public void SetUp() 28 | { 29 | _mocker = new AutoMocker(); 30 | 31 | var customer = new CustomerModel 32 | { 33 | Id = CustomerId, 34 | Name = CustomerName, 35 | }; 36 | 37 | var employee = new EmployeeModel() 38 | { 39 | Id = EmployeeId, 40 | Name = EmployeeName 41 | }; 42 | 43 | var product = new ProductModel 44 | { 45 | Id = ProductId, 46 | Name = ProductName, 47 | UnitPrice = ProductPrice 48 | }; 49 | 50 | _mocker.GetMock() 51 | .Setup(p => p.Execute()) 52 | .Returns(new List { customer }); 53 | 54 | _mocker.GetMock() 55 | .Setup(p => p.Execute()) 56 | .Returns(new List { employee }); 57 | 58 | _mocker.GetMock() 59 | .Setup(p => p.Execute()) 60 | .Returns(new List { product }); 61 | 62 | _factory = _mocker.CreateInstance(); 63 | } 64 | 65 | [Test] 66 | public void TestCreateShouldSetCustomers() 67 | { 68 | var viewModel = _factory.Create(); 69 | 70 | var result = viewModel.Customers.Single(); 71 | 72 | Assert.That(result.Value, 73 | Is.EqualTo(CustomerId.ToString())); 74 | 75 | Assert.That(result.Text, 76 | Is.EqualTo(CustomerName)); 77 | } 78 | 79 | [Test] 80 | public void TestCreateShouldSetEmployees() 81 | { 82 | var viewModel = _factory.Create(); 83 | 84 | var result = viewModel.Employees.Single(); 85 | 86 | Assert.That(result.Value, 87 | Is.EqualTo(EmployeeId.ToString())); 88 | 89 | Assert.That(result.Text, 90 | Is.EqualTo(EmployeeName)); 91 | } 92 | 93 | [Test] 94 | public void TestCreateShouldSetProducts() 95 | { 96 | var viewModel = _factory.Create(); 97 | 98 | var result = viewModel.Products.Single(); 99 | 100 | Assert.That(result.Value, 101 | Is.EqualTo(ProductId.ToString())); 102 | 103 | Assert.That(result.Text, 104 | Is.EqualTo("Product 3 ($1.23)")); 105 | } 106 | 107 | [Test] 108 | public void TestCreateShouldCreateEmptySaleModel() 109 | { 110 | var viewModel = _factory.Create(); 111 | 112 | var result = viewModel.Sale; 113 | 114 | Assert.That(result, Is.Not.Null); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /Presentation/Sales/Services/ICreateSaleViewModelFactory.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitecture.Presentation.Sales.Models; 2 | 3 | namespace CleanArchitecture.Presentation.Sales.Services 4 | { 5 | public interface ICreateSaleViewModelFactory 6 | { 7 | CreateSaleViewModel Create(); 8 | } 9 | } -------------------------------------------------------------------------------- /Presentation/Sales/Views/Create.cshtml: -------------------------------------------------------------------------------- 1 | @model CleanArchitecture.Presentation.Sales.Models.CreateSaleViewModel 2 | 3 | @{ 4 | ViewBag.Title = "Create Sale"; 5 | } 6 | 7 |

Create Sale

8 | 9 | @using (Html.BeginForm("create", "sales", FormMethod.Post)) 10 | { 11 |
12 |
13 | 14 | @Html.DropDownListFor(p => p.Sale.CustomerId, Model.Customers, new { @class="form-control" }) 15 |
16 |
17 | 18 | @Html.DropDownListFor(p => p.Sale.EmployeeId, Model.Employees, new { @class = "form-control" }) 19 |
20 |
21 | 22 | @Html.DropDownListFor(p => p.Sale.ProductId, Model.Products, new { @class = "form-control" }) 23 |
24 |
25 | 26 | @Html.TextBoxFor(p => p.Sale.Quantity, new { @class = "form-control" }) 27 |
28 | 29 |
30 | } 31 | -------------------------------------------------------------------------------- /Presentation/Sales/Views/Detail.cshtml: -------------------------------------------------------------------------------- 1 | @model CleanArchitecture.Application.Sales.Queries.GetSaleDetail.SaleDetailModel 2 | 3 | @{ 4 | ViewBag.Title = "Sale"; 5 | } 6 | 7 |

Sale

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
ID@Model.Id
Date@Model.Date.ToString("d")
Customer@Model.CustomerName
Employee@Model.EmployeeName
Product@Model.ProductName
Unit Price$@Model.UnitPrice
Quantity@Model.Quantity
Total Price$@Model.TotalPrice
42 | 43 | -------------------------------------------------------------------------------- /Presentation/Sales/Views/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 | @{ 4 | ViewBag.Title = "Sales"; 5 | } 6 | 7 |

Sales

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | @foreach (var sale in Model) 23 | { 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | } 35 | 36 |
IDDateCustomerEmployeeProductUnit PriceQuantityTotal Price
@sale.Id@sale.Date.ToString("d")@sale.CustomerName@sale.EmployeeName@sale.ProductName$@sale.UnitPrice@sale.Quantity$@sale.TotalPrice
37 |
38 | Create Sale 39 |
40 | 41 | -------------------------------------------------------------------------------- /Presentation/Shared/Views/Errors.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | -------------------------------------------------------------------------------- /Presentation/Shared/Views/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Clean Architecture in ASP.NET MVC 5 6 | 7 | 8 | 9 | 10 | 31 |
32 | @RenderBody() 33 |
34 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /Presentation/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Shared/Views/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /Presentation/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Presentation/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "CleanArchitectureCore": "Server=(local);Database=CleanArchitectureCore;Trusted_Connection=True;" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Architecture Demo 2 | A sample application for [Clean Architecture: Patterns, Practices, and Principles](https://pluralsight.pxf.io/clean-architecture) using Microsoft .NET Core 6.0. 3 | 4 | This sample application is intended to be a learning tool for clean architecture practices. It incorporates several of these practices in a way that is simple and easy to understand. 5 | 6 | If you'd like to learn more about this style of software architecture, please check out my online course [Clean Architecture: Patterns, Practices, and Principles](https://pluralsight.pxf.io/clean-architecture). 7 | 8 | ## Technologies 9 | This demo application uses the following technologies: 10 | - .NET Core 6.0 11 | - C# .NET 10.0 12 | - ASP.NET Core MVC 6.0 13 | - EF Core 6.0 14 | - Visual Studio 2022 15 | - SQL Server 2019 16 | - Scrutor 4.2 17 | - NUnit 3.13 18 | - Moq 4.18 19 | - SpecFlow 3.9 20 | 21 | ## Other Versions 22 | For other versions of this sample application, please see the following: 23 | - [.NET Framework 4.8](https://github.com/matthewrenze/clean-architecture-demo) 24 | - [.NET Framework 4.5](https://github.com/matthewrenze/clean-architecture-demo/tree/v4.5) -------------------------------------------------------------------------------- /Service/Customers/CustomersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using CleanArchitecture.Application.Customers.Queries.GetCustomerList; 6 | 7 | namespace CleanArchitecture.Service.Customers 8 | { 9 | [ApiController] 10 | [Route("api/[controller]")] 11 | public class CustomersController : ControllerBase 12 | { 13 | private readonly IGetCustomersListQuery _query; 14 | 15 | public CustomersController(IGetCustomersListQuery query) 16 | { 17 | _query = query; 18 | } 19 | 20 | [HttpGet] 21 | public IEnumerable Get() 22 | { 23 | return _query.Execute(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Service/Customers/CustomersControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using Moq.AutoMock; 6 | using CleanArchitecture.Application.Customers.Queries.GetCustomerList; 7 | using NUnit.Framework; 8 | 9 | namespace CleanArchitecture.Service.Customers 10 | { 11 | [TestFixture] 12 | public class CustomersControllerTests 13 | { 14 | private CustomersController _controller; 15 | private AutoMocker _mocker; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _mocker = new AutoMocker(); 21 | 22 | _controller = _mocker.CreateInstance(); 23 | } 24 | 25 | [Test] 26 | public void TestGetCustomersListShouldReturnListOfCustomers() 27 | { 28 | var customer = new CustomerModel(); 29 | 30 | _mocker.GetMock() 31 | .Setup(p => p.Execute()) 32 | .Returns(new List { customer }); 33 | 34 | var results = _controller.Get(); 35 | 36 | Assert.That(results, 37 | Contains.Item(customer)); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Service/Employees/EmployeesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using CleanArchitecture.Application.Employees.Queries.GetEmployeesList; 6 | 7 | namespace CleanArchitecture.Service.Employees 8 | { 9 | [ApiController] 10 | [Route("api/[controller]")] 11 | public class EmployeesController : ControllerBase 12 | { 13 | private readonly IGetEmployeesListQuery _query; 14 | 15 | public EmployeesController(IGetEmployeesListQuery query) 16 | { 17 | _query = query; 18 | } 19 | 20 | [HttpGet] 21 | public IEnumerable Get() 22 | { 23 | return _query.Execute(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Service/Employees/EmployeesControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using Moq.AutoMock; 6 | using CleanArchitecture.Application.Employees.Queries.GetEmployeesList; 7 | using NUnit.Framework; 8 | 9 | namespace CleanArchitecture.Service.Employees 10 | { 11 | [TestFixture] 12 | public class EmployeesControllerTests 13 | { 14 | private EmployeesController _controller; 15 | private AutoMocker _mocker; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _mocker = new AutoMocker(); 21 | 22 | _controller = _mocker.CreateInstance(); 23 | } 24 | 25 | [Test] 26 | public void TestGetEmployeesListShouldReturnListOfEmployees() 27 | { 28 | var employee = new EmployeeModel(); 29 | 30 | _mocker.GetMock() 31 | .Setup(p => p.Execute()) 32 | .Returns(new List {employee}); 33 | 34 | var results = _controller.Get(); 35 | 36 | Assert.That(results, 37 | Contains.Item(employee)); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Service/LowercaseDocumentFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | using System.Collections.Generic; 4 | 5 | //TODO: Move to Shared folder or Common project? 6 | 7 | namespace CleanArchitecture.Service 8 | { 9 | public class LowercaseDocumentFilter : IDocumentFilter 10 | { 11 | public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) 12 | { 13 | var paths = swaggerDoc.Paths; 14 | 15 | var newPaths = new Dictionary(); 16 | 17 | var removeKeys = new List(); 18 | 19 | foreach (var path in paths) 20 | { 21 | var newKey = path.Key.ToLower(); 22 | 23 | if (newKey != path.Key) 24 | { 25 | removeKeys.Add(path.Key); 26 | 27 | newPaths.Add(newKey, path.Value); 28 | } 29 | } 30 | 31 | foreach (var path in newPaths) 32 | swaggerDoc.Paths.Add(path.Key, path.Value); 33 | 34 | foreach (var key in removeKeys) 35 | swaggerDoc.Paths.Remove(key); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Service/Products/ProductsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using CleanArchitecture.Application.Products.Queries.GetProductsList; 6 | 7 | namespace CleanArchitecture.Service.Products 8 | { 9 | [ApiController] 10 | [Route("api/[controller]")] 11 | public class ProductsController : ControllerBase 12 | { 13 | private readonly IGetProductsListQuery _query; 14 | 15 | public ProductsController(IGetProductsListQuery query) 16 | { 17 | _query = query; 18 | } 19 | 20 | [HttpGet] 21 | public IEnumerable Get() 22 | { 23 | return _query.Execute(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Service/Products/ProductsControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using Moq.AutoMock; 6 | using CleanArchitecture.Application.Products.Queries.GetProductsList; 7 | using NUnit.Framework; 8 | 9 | namespace CleanArchitecture.Service.Products 10 | { 11 | [TestFixture] 12 | public class ProductsControllerTests 13 | { 14 | private ProductsController _controller; 15 | private AutoMocker _mocker; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _mocker = new AutoMocker(); 21 | 22 | _controller = _mocker.CreateInstance(); 23 | } 24 | 25 | [Test] 26 | public void TestGetProductsListShouldReturnListOfProducts() 27 | { 28 | var product = new ProductModel(); 29 | 30 | _mocker.GetMock() 31 | .Setup(p => p.Execute()) 32 | .Returns(new List { product }); 33 | 34 | var results = _controller.Get(); 35 | 36 | Assert.That(results, 37 | Contains.Item(product)); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Service/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Loader; 2 | 3 | namespace CleanArchitecture.Service; 4 | 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | var files = Directory.GetFiles( 10 | AppDomain.CurrentDomain.BaseDirectory, 11 | "CleanArchitecture*.dll"); 12 | 13 | var assemblies = files 14 | .Select(p => AssemblyLoadContext.Default.LoadFromAssemblyPath(p)); 15 | 16 | var builder = WebApplication.CreateBuilder(args); 17 | 18 | builder.Services.AddControllers(); 19 | 20 | builder.Services.AddEndpointsApiExplorer(); 21 | 22 | builder.Services.AddSwaggerGen( 23 | p => p.DocumentFilter()); 24 | 25 | builder.Services.AddAdvancedDependencyInjection(); 26 | 27 | builder.Services.Scan(p => p.FromAssemblies(assemblies) 28 | .AddClasses() 29 | .AsMatchingInterface()); 30 | 31 | var app = builder.Build(); 32 | 33 | if (app.Environment.IsDevelopment()) 34 | { 35 | app.UseSwagger(); 36 | app.UseSwaggerUI(); 37 | } 38 | 39 | app.UseHttpsRedirection(); 40 | 41 | app.UseAuthorization(); 42 | 43 | app.MapControllers(); 44 | 45 | app.UseAdvancedDependencyInjection(); 46 | 47 | app.Run(); 48 | } 49 | } -------------------------------------------------------------------------------- /Service/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:1413", 8 | "sslPort": 44300 9 | } 10 | }, 11 | "profiles": { 12 | "Service": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "https://localhost:7001;http://localhost:5001", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Service/Sales/SalesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using Microsoft.AspNetCore.Mvc; 6 | using CleanArchitecture.Application.Sales.Commands.CreateSale; 7 | using CleanArchitecture.Application.Sales.Queries.GetSaleDetail; 8 | using CleanArchitecture.Application.Sales.Queries.GetSalesList; 9 | 10 | namespace CleanArchitecture.Service.Sales 11 | { 12 | [ApiController] 13 | [Route("api/[controller]")] 14 | public class SalesController : ControllerBase 15 | { 16 | private readonly IGetSalesListQuery _listQuery; 17 | private readonly IGetSaleDetailQuery _detailQuery; 18 | private readonly ICreateSaleCommand _createCommand; 19 | 20 | public SalesController( 21 | IGetSalesListQuery listQuery, 22 | IGetSaleDetailQuery detailQuery, 23 | ICreateSaleCommand createCommand) 24 | { 25 | _listQuery = listQuery; 26 | _detailQuery = detailQuery; 27 | _createCommand = createCommand; 28 | } 29 | 30 | [HttpGet] 31 | public IEnumerable Get() 32 | { 33 | return _listQuery.Execute(); 34 | } 35 | 36 | [HttpGet("{id}")] 37 | public SaleDetailModel Get(int id) 38 | { 39 | return _detailQuery.Execute(id); 40 | } 41 | 42 | [HttpPost] 43 | public HttpResponseMessage Create(CreateSaleModel sale) 44 | { 45 | _createCommand.Execute(sale); 46 | 47 | return new HttpResponseMessage(HttpStatusCode.Created); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Service/Sales/SalesControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using Moq.AutoMock; 6 | using CleanArchitecture.Application.Sales.Commands.CreateSale; 7 | using CleanArchitecture.Application.Sales.Queries.GetSaleDetail; 8 | using CleanArchitecture.Application.Sales.Queries.GetSalesList; 9 | using Moq; 10 | using NUnit.Framework; 11 | 12 | namespace CleanArchitecture.Service.Sales 13 | { 14 | [TestFixture] 15 | public class SalesControllerTests 16 | { 17 | private SalesController _controller; 18 | private AutoMocker _mocker; 19 | 20 | [SetUp] 21 | public void SetUp() 22 | { 23 | _mocker = new AutoMocker(); 24 | 25 | _controller = _mocker.CreateInstance(); 26 | } 27 | 28 | [Test] 29 | public void TestGetShouldReturnListOfSales() 30 | { 31 | var sale = new SalesListItemModel(); 32 | 33 | _mocker.GetMock() 34 | .Setup(p => p.Execute()) 35 | .Returns(new List {sale} ); 36 | 37 | var result = _controller.Get(); 38 | 39 | Assert.That(result, 40 | Contains.Item(sale)); 41 | } 42 | 43 | [Test] 44 | public void TestGetShouldReturnSaleDetails() 45 | { 46 | var sale = new SaleDetailModel(); 47 | 48 | _mocker.GetMock() 49 | .Setup(p => p.Execute(1)) 50 | .Returns(sale); 51 | 52 | var result = _controller.Get(1); 53 | 54 | Assert.That(result, 55 | Is.EqualTo(sale)); 56 | } 57 | 58 | [Test] 59 | public void TestCreateSaleShouldCreateSale() 60 | { 61 | var sale = new CreateSaleModel(); 62 | 63 | var result = _controller.Create(sale); 64 | 65 | _mocker.GetMock() 66 | .Verify(p => p.Execute(sale), 67 | Times.Once); 68 | 69 | Assert.That(result.StatusCode, 70 | Is.EqualTo(HttpStatusCode.Created)); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /Service/Service.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | disable 6 | enable 7 | CleanArchitecture.Service.Program 8 | CleanArchitecture.$(MSBuildProjectName) 9 | CleanArchitecture.$(MSBuildProjectName.Replace(" ", "_")) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Service/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Service/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "CleanArchitectureCore": "Server=(local);Database=CleanArchitectureCore;Trusted_Connection=True;" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Specification/Customers/GetCustomersList/GetCustomersList.feature: -------------------------------------------------------------------------------- 1 | Feature: Get Customers List 2 | As a sales person 3 | I want to get a list of customers 4 | So I can inspect the customers 5 | 6 | Scenario: Get a List of Customers 7 | When I request a list of customers 8 | Then the following customers should be returned: 9 | | Id | Name | 10 | | 1 | Martin Fowler | 11 | | 2 | Uncle Bob | 12 | | 3 | Kent Beck | 13 | -------------------------------------------------------------------------------- /Specification/Customers/GetCustomersList/GetCustomersList.feature.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by SpecFlow (https://www.specflow.org/). 4 | // SpecFlow Version:3.9.0.0 5 | // SpecFlow Generator Version:3.9.0.0 6 | // 7 | // Changes to this file may cause incorrect behavior and will be lost if 8 | // the code is regenerated. 9 | // 10 | // ------------------------------------------------------------------------------ 11 | #region Designer generated code 12 | #pragma warning disable 13 | namespace CleanArchitecture.Specification.Customers.GetCustomersList 14 | { 15 | using TechTalk.SpecFlow; 16 | using System; 17 | using System.Linq; 18 | 19 | 20 | [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] 21 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 22 | [NUnit.Framework.TestFixtureAttribute()] 23 | [NUnit.Framework.DescriptionAttribute("Get Customers List")] 24 | public partial class GetCustomersListFeature 25 | { 26 | 27 | private TechTalk.SpecFlow.ITestRunner testRunner; 28 | 29 | private string[] _featureTags = ((string[])(null)); 30 | 31 | #line 1 "GetCustomersList.feature" 32 | #line hidden 33 | 34 | [NUnit.Framework.OneTimeSetUpAttribute()] 35 | public virtual void FeatureSetup() 36 | { 37 | testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); 38 | TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Customers/GetCustomersList", "Get Customers List", "\tAs a sales person\r\n\tI want to get a list of customers\r\n\tSo I can inspect the cus" + 39 | "tomers", ProgrammingLanguage.CSharp, ((string[])(null))); 40 | testRunner.OnFeatureStart(featureInfo); 41 | } 42 | 43 | [NUnit.Framework.OneTimeTearDownAttribute()] 44 | public virtual void FeatureTearDown() 45 | { 46 | testRunner.OnFeatureEnd(); 47 | testRunner = null; 48 | } 49 | 50 | [NUnit.Framework.SetUpAttribute()] 51 | public virtual void TestInitialize() 52 | { 53 | } 54 | 55 | [NUnit.Framework.TearDownAttribute()] 56 | public virtual void TestTearDown() 57 | { 58 | testRunner.OnScenarioEnd(); 59 | } 60 | 61 | public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) 62 | { 63 | testRunner.OnScenarioInitialize(scenarioInfo); 64 | testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); 65 | } 66 | 67 | public virtual void ScenarioStart() 68 | { 69 | testRunner.OnScenarioStart(); 70 | } 71 | 72 | public virtual void ScenarioCleanup() 73 | { 74 | testRunner.CollectScenarioErrors(); 75 | } 76 | 77 | [NUnit.Framework.TestAttribute()] 78 | [NUnit.Framework.DescriptionAttribute("Get a List of Customers")] 79 | public virtual void GetAListOfCustomers() 80 | { 81 | string[] tagsOfScenario = ((string[])(null)); 82 | System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); 83 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Get a List of Customers", null, tagsOfScenario, argumentsOfScenario, this._featureTags); 84 | #line 6 85 | this.ScenarioInitialize(scenarioInfo); 86 | #line hidden 87 | bool isScenarioIgnored = default(bool); 88 | bool isFeatureIgnored = default(bool); 89 | if ((tagsOfScenario != null)) 90 | { 91 | isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 92 | } 93 | if ((this._featureTags != null)) 94 | { 95 | isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 96 | } 97 | if ((isScenarioIgnored || isFeatureIgnored)) 98 | { 99 | testRunner.SkipScenario(); 100 | } 101 | else 102 | { 103 | this.ScenarioStart(); 104 | #line 7 105 | testRunner.When("I request a list of customers", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 106 | #line hidden 107 | TechTalk.SpecFlow.Table table1 = new TechTalk.SpecFlow.Table(new string[] { 108 | "Id", 109 | "Name"}); 110 | table1.AddRow(new string[] { 111 | "1", 112 | "Martin Fowler"}); 113 | table1.AddRow(new string[] { 114 | "2", 115 | "Uncle Bob"}); 116 | table1.AddRow(new string[] { 117 | "3", 118 | "Kent Beck"}); 119 | #line 8 120 | testRunner.Then("the following customers should be returned:", ((string)(null)), table1, "Then "); 121 | #line hidden 122 | } 123 | this.ScenarioCleanup(); 124 | } 125 | } 126 | } 127 | #pragma warning restore 128 | #endregion 129 | -------------------------------------------------------------------------------- /Specification/Customers/GetCustomersList/GetCustomersListSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Customers.Queries.GetCustomerList; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using NUnit.Framework; 7 | using TechTalk.SpecFlow; 8 | using TechTalk.SpecFlow.Assist; 9 | using AppContext = CleanArchitecture.Specification.Shared.AppContext; 10 | 11 | namespace CleanArchitecture.Specification.Customers.GetCustomersList 12 | { 13 | [Binding] 14 | public class GetCustomersListSteps 15 | { 16 | private readonly AppContext _context; 17 | private List _results; 18 | 19 | public GetCustomersListSteps(AppContext context) 20 | { 21 | _context = context; 22 | _results = new List(); 23 | } 24 | 25 | [When(@"I request a list of customers")] 26 | public void WhenIRequestAListOfCustomers() 27 | { 28 | var query = _context.Container 29 | .GetService(); 30 | 31 | _results = query.Execute(); 32 | } 33 | 34 | [Then(@"the following customers should be returned:")] 35 | public void ThenTheFollowingCustomersShouldBeReturned(Table table) 36 | { 37 | var models = table.CreateSet().ToList(); 38 | 39 | for (var i = 0; i < models.Count; i++) 40 | { 41 | var model = models[i]; 42 | 43 | var result = _results[i]; 44 | 45 | Assert.That(result.Id, 46 | Is.EqualTo(model.Id)); 47 | 48 | Assert.That(result.Name, 49 | Is.EqualTo(model.Name)); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Specification/Employees/GetEmployeesList/GetEmployeesList.feature: -------------------------------------------------------------------------------- 1 | Feature: Get Employees List 2 | As a sales person 3 | I want to get a list of employees 4 | So I can inspect the employees 5 | 6 | Scenario: Get a List of Employees 7 | When I request a list of employees 8 | Then the following employees should be returned: 9 | | Id | Name | 10 | | 1 | Eric Evans | 11 | | 2 | Greg Young | 12 | | 3 | Udi Dahan | 13 | -------------------------------------------------------------------------------- /Specification/Employees/GetEmployeesList/GetEmployeesList.feature.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by SpecFlow (https://www.specflow.org/). 4 | // SpecFlow Version:3.9.0.0 5 | // SpecFlow Generator Version:3.9.0.0 6 | // 7 | // Changes to this file may cause incorrect behavior and will be lost if 8 | // the code is regenerated. 9 | // 10 | // ------------------------------------------------------------------------------ 11 | #region Designer generated code 12 | #pragma warning disable 13 | namespace CleanArchitecture.Specification.Employees.GetEmployeesList 14 | { 15 | using TechTalk.SpecFlow; 16 | using System; 17 | using System.Linq; 18 | 19 | 20 | [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] 21 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 22 | [NUnit.Framework.TestFixtureAttribute()] 23 | [NUnit.Framework.DescriptionAttribute("Get Employees List")] 24 | public partial class GetEmployeesListFeature 25 | { 26 | 27 | private TechTalk.SpecFlow.ITestRunner testRunner; 28 | 29 | private string[] _featureTags = ((string[])(null)); 30 | 31 | #line 1 "GetEmployeesList.feature" 32 | #line hidden 33 | 34 | [NUnit.Framework.OneTimeSetUpAttribute()] 35 | public virtual void FeatureSetup() 36 | { 37 | testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); 38 | TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Employees/GetEmployeesList", "Get Employees List", "\tAs a sales person\r\n\tI want to get a list of employees\r\n\tSo I can inspect the emp" + 39 | "loyees", ProgrammingLanguage.CSharp, ((string[])(null))); 40 | testRunner.OnFeatureStart(featureInfo); 41 | } 42 | 43 | [NUnit.Framework.OneTimeTearDownAttribute()] 44 | public virtual void FeatureTearDown() 45 | { 46 | testRunner.OnFeatureEnd(); 47 | testRunner = null; 48 | } 49 | 50 | [NUnit.Framework.SetUpAttribute()] 51 | public virtual void TestInitialize() 52 | { 53 | } 54 | 55 | [NUnit.Framework.TearDownAttribute()] 56 | public virtual void TestTearDown() 57 | { 58 | testRunner.OnScenarioEnd(); 59 | } 60 | 61 | public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) 62 | { 63 | testRunner.OnScenarioInitialize(scenarioInfo); 64 | testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); 65 | } 66 | 67 | public virtual void ScenarioStart() 68 | { 69 | testRunner.OnScenarioStart(); 70 | } 71 | 72 | public virtual void ScenarioCleanup() 73 | { 74 | testRunner.CollectScenarioErrors(); 75 | } 76 | 77 | [NUnit.Framework.TestAttribute()] 78 | [NUnit.Framework.DescriptionAttribute("Get a List of Employees")] 79 | public virtual void GetAListOfEmployees() 80 | { 81 | string[] tagsOfScenario = ((string[])(null)); 82 | System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); 83 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Get a List of Employees", null, tagsOfScenario, argumentsOfScenario, this._featureTags); 84 | #line 6 85 | this.ScenarioInitialize(scenarioInfo); 86 | #line hidden 87 | bool isScenarioIgnored = default(bool); 88 | bool isFeatureIgnored = default(bool); 89 | if ((tagsOfScenario != null)) 90 | { 91 | isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 92 | } 93 | if ((this._featureTags != null)) 94 | { 95 | isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 96 | } 97 | if ((isScenarioIgnored || isFeatureIgnored)) 98 | { 99 | testRunner.SkipScenario(); 100 | } 101 | else 102 | { 103 | this.ScenarioStart(); 104 | #line 7 105 | testRunner.When("I request a list of employees", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 106 | #line hidden 107 | TechTalk.SpecFlow.Table table2 = new TechTalk.SpecFlow.Table(new string[] { 108 | "Id", 109 | "Name"}); 110 | table2.AddRow(new string[] { 111 | "1", 112 | "Eric Evans"}); 113 | table2.AddRow(new string[] { 114 | "2", 115 | "Greg Young"}); 116 | table2.AddRow(new string[] { 117 | "3", 118 | "Udi Dahan"}); 119 | #line 8 120 | testRunner.Then("the following employees should be returned:", ((string)(null)), table2, "Then "); 121 | #line hidden 122 | } 123 | this.ScenarioCleanup(); 124 | } 125 | } 126 | } 127 | #pragma warning restore 128 | #endregion 129 | -------------------------------------------------------------------------------- /Specification/Employees/GetEmployeesList/GetEmployeesListSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Employees.Queries.GetEmployeesList; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using NUnit.Framework; 7 | using TechTalk.SpecFlow; 8 | using TechTalk.SpecFlow.Assist; 9 | using AppContext = CleanArchitecture.Specification.Shared.AppContext; 10 | 11 | namespace CleanArchitecture.Specification.Employees.GetEmployeesList 12 | { 13 | [Binding] 14 | public class GetEmployeesListSteps 15 | { 16 | private readonly AppContext _context; 17 | private List _results; 18 | 19 | public GetEmployeesListSteps(AppContext context) 20 | { 21 | _context = context; 22 | _results = new List(); 23 | } 24 | 25 | [When(@"I request a list of employees")] 26 | public void WhenIRequestAListOfEmployees() 27 | { 28 | var query = _context.Container 29 | .GetService(); 30 | 31 | _results = query.Execute(); 32 | } 33 | 34 | [Then(@"the following employees should be returned:")] 35 | public void ThenTheFollowingEmployeesShouldBeReturned(Table table) 36 | { 37 | var models = table.CreateSet().ToList(); 38 | 39 | for (var i = 0; i < models.Count; i++) 40 | { 41 | var model = models[i]; 42 | 43 | var result = _results[i]; 44 | 45 | Assert.That(result.Id, 46 | Is.EqualTo(model.Id)); 47 | 48 | Assert.That(result.Name, 49 | Is.EqualTo(model.Name)); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Specification/Products/GetProductsList.feature: -------------------------------------------------------------------------------- 1 | Feature: Get Products List 2 | As a sales person 3 | I want to get a list of products 4 | So I can inspect the products 5 | 6 | Scenario: Get a List of Products 7 | When I request a list of products 8 | Then the following products should be returned: 9 | | Id | Name | Unit Price | 10 | | 1 | Spaghetti | 5.00 | 11 | | 2 | Lasagne | 10.00 | 12 | | 3 | Ravioli | 15.00 | 13 | -------------------------------------------------------------------------------- /Specification/Products/GetProductsList.feature.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by SpecFlow (https://www.specflow.org/). 4 | // SpecFlow Version:3.9.0.0 5 | // SpecFlow Generator Version:3.9.0.0 6 | // 7 | // Changes to this file may cause incorrect behavior and will be lost if 8 | // the code is regenerated. 9 | // 10 | // ------------------------------------------------------------------------------ 11 | #region Designer generated code 12 | #pragma warning disable 13 | namespace CleanArchitecture.Specification.Products 14 | { 15 | using TechTalk.SpecFlow; 16 | using System; 17 | using System.Linq; 18 | 19 | 20 | [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] 21 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 22 | [NUnit.Framework.TestFixtureAttribute()] 23 | [NUnit.Framework.DescriptionAttribute("Get Products List")] 24 | public partial class GetProductsListFeature 25 | { 26 | 27 | private TechTalk.SpecFlow.ITestRunner testRunner; 28 | 29 | private string[] _featureTags = ((string[])(null)); 30 | 31 | #line 1 "GetProductsList.feature" 32 | #line hidden 33 | 34 | [NUnit.Framework.OneTimeSetUpAttribute()] 35 | public virtual void FeatureSetup() 36 | { 37 | testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); 38 | TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Products", "Get Products List", "\tAs a sales person\r\n\tI want to get a list of products\r\n\tSo I can inspect the prod" + 39 | "ucts", ProgrammingLanguage.CSharp, ((string[])(null))); 40 | testRunner.OnFeatureStart(featureInfo); 41 | } 42 | 43 | [NUnit.Framework.OneTimeTearDownAttribute()] 44 | public virtual void FeatureTearDown() 45 | { 46 | testRunner.OnFeatureEnd(); 47 | testRunner = null; 48 | } 49 | 50 | [NUnit.Framework.SetUpAttribute()] 51 | public virtual void TestInitialize() 52 | { 53 | } 54 | 55 | [NUnit.Framework.TearDownAttribute()] 56 | public virtual void TestTearDown() 57 | { 58 | testRunner.OnScenarioEnd(); 59 | } 60 | 61 | public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) 62 | { 63 | testRunner.OnScenarioInitialize(scenarioInfo); 64 | testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); 65 | } 66 | 67 | public virtual void ScenarioStart() 68 | { 69 | testRunner.OnScenarioStart(); 70 | } 71 | 72 | public virtual void ScenarioCleanup() 73 | { 74 | testRunner.CollectScenarioErrors(); 75 | } 76 | 77 | [NUnit.Framework.TestAttribute()] 78 | [NUnit.Framework.DescriptionAttribute("Get a List of Products")] 79 | public virtual void GetAListOfProducts() 80 | { 81 | string[] tagsOfScenario = ((string[])(null)); 82 | System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); 83 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Get a List of Products", null, tagsOfScenario, argumentsOfScenario, this._featureTags); 84 | #line 6 85 | this.ScenarioInitialize(scenarioInfo); 86 | #line hidden 87 | bool isScenarioIgnored = default(bool); 88 | bool isFeatureIgnored = default(bool); 89 | if ((tagsOfScenario != null)) 90 | { 91 | isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 92 | } 93 | if ((this._featureTags != null)) 94 | { 95 | isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 96 | } 97 | if ((isScenarioIgnored || isFeatureIgnored)) 98 | { 99 | testRunner.SkipScenario(); 100 | } 101 | else 102 | { 103 | this.ScenarioStart(); 104 | #line 7 105 | testRunner.When("I request a list of products", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 106 | #line hidden 107 | TechTalk.SpecFlow.Table table3 = new TechTalk.SpecFlow.Table(new string[] { 108 | "Id", 109 | "Name", 110 | "Unit Price"}); 111 | table3.AddRow(new string[] { 112 | "1", 113 | "Spaghetti", 114 | "5.00"}); 115 | table3.AddRow(new string[] { 116 | "2", 117 | "Lasagne", 118 | "10.00"}); 119 | table3.AddRow(new string[] { 120 | "3", 121 | "Ravioli", 122 | "15.00"}); 123 | #line 8 124 | testRunner.Then("the following products should be returned:", ((string)(null)), table3, "Then "); 125 | #line hidden 126 | } 127 | this.ScenarioCleanup(); 128 | } 129 | } 130 | } 131 | #pragma warning restore 132 | #endregion 133 | -------------------------------------------------------------------------------- /Specification/Products/GetProductsListSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Products.Queries.GetProductsList; 5 | using CleanArchitecture.Specification.Shared; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using NUnit.Framework; 8 | using TechTalk.SpecFlow; 9 | using TechTalk.SpecFlow.Assist; 10 | using AppContext = CleanArchitecture.Specification.Shared.AppContext; 11 | 12 | namespace CleanArchitecture.Specification.Products 13 | { 14 | [Binding] 15 | public class GetProductsListSteps 16 | { 17 | private readonly AppContext _context; 18 | private List _results; 19 | 20 | public GetProductsListSteps(AppContext context) 21 | { 22 | _context = context; 23 | _results = new List(); 24 | } 25 | 26 | [When(@"I request a list of products")] 27 | public void WhenIRequestAListOfProducts() 28 | { 29 | var query = _context.Container 30 | .GetService(); 31 | 32 | _results = query.Execute(); 33 | } 34 | 35 | [Then(@"the following products should be returned:")] 36 | public void ThenTheFollowingProductsShouldBeReturned(Table table) 37 | { 38 | var models = table.CreateSet().ToList(); 39 | 40 | for (var i = 0; i < models.Count; i++) 41 | { 42 | var model = models[i]; 43 | 44 | var result = _results[i]; 45 | 46 | Assert.That(result.Id, 47 | Is.EqualTo(model.Id)); 48 | 49 | Assert.That(result.Name, 50 | Is.EqualTo(model.Name)); 51 | 52 | Assert.That(result.UnitPrice, 53 | Is.EqualTo(model.UnitPrice)); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Specification/Sales/CreateASale/CreateASale.feature: -------------------------------------------------------------------------------- 1 | Feature: Create a Sale 2 | As a sales person 3 | I want to create a sale 4 | To record a sales transaction 5 | 6 | Scenario: Create a Sale 7 | Given the following sale info: 8 | | Date | Customer | Employee | Product | Quantity | 9 | | 2001-02-03 | Martin Fowler | Eric Evans | Spaghetti | 2 | 10 | When I create a sale 11 | Then the following sales record should be recorded: 12 | | Date | Customer | Employee | Product | Unit Price | Quantity | Total Price | 13 | | 2001-02-03 | Martin Fowler | Eric Evans | Spaghetti | 5.00 | 2 | 10.00 | 14 | And the following sale-occurred notification should be sent to the inventory system: 15 | | Product ID | Quantity | 16 | | 1 | 2 | -------------------------------------------------------------------------------- /Specification/Sales/CreateASale/CreateASale.feature.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by SpecFlow (https://www.specflow.org/). 4 | // SpecFlow Version:3.9.0.0 5 | // SpecFlow Generator Version:3.9.0.0 6 | // 7 | // Changes to this file may cause incorrect behavior and will be lost if 8 | // the code is regenerated. 9 | // 10 | // ------------------------------------------------------------------------------ 11 | #region Designer generated code 12 | #pragma warning disable 13 | namespace CleanArchitecture.Specification.Sales.CreateASale 14 | { 15 | using TechTalk.SpecFlow; 16 | using System; 17 | using System.Linq; 18 | 19 | 20 | [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] 21 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 22 | [NUnit.Framework.TestFixtureAttribute()] 23 | [NUnit.Framework.DescriptionAttribute("Create a Sale")] 24 | public partial class CreateASaleFeature 25 | { 26 | 27 | private TechTalk.SpecFlow.ITestRunner testRunner; 28 | 29 | private string[] _featureTags = ((string[])(null)); 30 | 31 | #line 1 "CreateASale.feature" 32 | #line hidden 33 | 34 | [NUnit.Framework.OneTimeSetUpAttribute()] 35 | public virtual void FeatureSetup() 36 | { 37 | testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); 38 | TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Sales/CreateASale", "Create a Sale", "\tAs a sales person\r\n\tI want to create a sale\r\n\tTo record a sales transaction", ProgrammingLanguage.CSharp, ((string[])(null))); 39 | testRunner.OnFeatureStart(featureInfo); 40 | } 41 | 42 | [NUnit.Framework.OneTimeTearDownAttribute()] 43 | public virtual void FeatureTearDown() 44 | { 45 | testRunner.OnFeatureEnd(); 46 | testRunner = null; 47 | } 48 | 49 | [NUnit.Framework.SetUpAttribute()] 50 | public virtual void TestInitialize() 51 | { 52 | } 53 | 54 | [NUnit.Framework.TearDownAttribute()] 55 | public virtual void TestTearDown() 56 | { 57 | testRunner.OnScenarioEnd(); 58 | } 59 | 60 | public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) 61 | { 62 | testRunner.OnScenarioInitialize(scenarioInfo); 63 | testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); 64 | } 65 | 66 | public virtual void ScenarioStart() 67 | { 68 | testRunner.OnScenarioStart(); 69 | } 70 | 71 | public virtual void ScenarioCleanup() 72 | { 73 | testRunner.CollectScenarioErrors(); 74 | } 75 | 76 | [NUnit.Framework.TestAttribute()] 77 | [NUnit.Framework.DescriptionAttribute("Create a Sale")] 78 | public virtual void CreateASale() 79 | { 80 | string[] tagsOfScenario = ((string[])(null)); 81 | System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); 82 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Create a Sale", null, tagsOfScenario, argumentsOfScenario, this._featureTags); 83 | #line 6 84 | this.ScenarioInitialize(scenarioInfo); 85 | #line hidden 86 | bool isScenarioIgnored = default(bool); 87 | bool isFeatureIgnored = default(bool); 88 | if ((tagsOfScenario != null)) 89 | { 90 | isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 91 | } 92 | if ((this._featureTags != null)) 93 | { 94 | isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 95 | } 96 | if ((isScenarioIgnored || isFeatureIgnored)) 97 | { 98 | testRunner.SkipScenario(); 99 | } 100 | else 101 | { 102 | this.ScenarioStart(); 103 | TechTalk.SpecFlow.Table table4 = new TechTalk.SpecFlow.Table(new string[] { 104 | "Date", 105 | "Customer", 106 | "Employee", 107 | "Product", 108 | "Quantity"}); 109 | table4.AddRow(new string[] { 110 | "2001-02-03", 111 | "Martin Fowler", 112 | "Eric Evans", 113 | "Spaghetti", 114 | "2"}); 115 | #line 7 116 | testRunner.Given("the following sale info:", ((string)(null)), table4, "Given "); 117 | #line hidden 118 | #line 10 119 | testRunner.When("I create a sale", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 120 | #line hidden 121 | TechTalk.SpecFlow.Table table5 = new TechTalk.SpecFlow.Table(new string[] { 122 | "Date", 123 | "Customer", 124 | "Employee", 125 | "Product", 126 | "Unit Price", 127 | "Quantity", 128 | "Total Price"}); 129 | table5.AddRow(new string[] { 130 | "2001-02-03", 131 | "Martin Fowler", 132 | "Eric Evans", 133 | "Spaghetti", 134 | "5.00", 135 | "2", 136 | "10.00"}); 137 | #line 11 138 | testRunner.Then("the following sales record should be recorded:", ((string)(null)), table5, "Then "); 139 | #line hidden 140 | TechTalk.SpecFlow.Table table6 = new TechTalk.SpecFlow.Table(new string[] { 141 | "Product ID", 142 | "Quantity"}); 143 | table6.AddRow(new string[] { 144 | "1", 145 | "2"}); 146 | #line 14 147 | testRunner.And("the following sale-occurred notification should be sent to the inventory system:", ((string)(null)), table6, "And "); 148 | #line hidden 149 | } 150 | this.ScenarioCleanup(); 151 | } 152 | } 153 | } 154 | #pragma warning restore 155 | #endregion 156 | -------------------------------------------------------------------------------- /Specification/Sales/CreateASale/CreateASaleSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CleanArchitecture.Application.Interfaces; 4 | using CleanArchitecture.Application.Sales.Commands.CreateSale; 5 | using CleanArchitecture.Common.Dates; 6 | using CleanArchitecture.Specification.Shared; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Moq; 9 | using NUnit.Framework; 10 | using TechTalk.SpecFlow; 11 | using TechTalk.SpecFlow.Assist; 12 | using AppContext = CleanArchitecture.Specification.Shared.AppContext; 13 | 14 | namespace CleanArchitecture.Specification.Sales.CreateASale 15 | { 16 | [Binding] 17 | public class CreateASaleSteps 18 | { 19 | private readonly AppContext _context; 20 | private CreateSaleModel _model; 21 | 22 | public CreateASaleSteps(AppContext context) 23 | { 24 | _context = context; 25 | _model = new CreateSaleModel(); 26 | } 27 | 28 | [Given(@"the following sale info:")] 29 | public void GivenTheFollowingSaleInfo(Table table) 30 | { 31 | var saleInfo = table.CreateInstance(); 32 | 33 | _context.Mocker.GetMock() 34 | .Setup(p => p.GetDate()) 35 | .Returns(saleInfo.Date); 36 | 37 | var mockDatabase = _context.DatabaseService; 38 | 39 | var lookup = new DatabaseLookup(mockDatabase); 40 | 41 | _model = new CreateSaleModel 42 | { 43 | CustomerId = lookup.GetCustomerId(saleInfo.Customer), 44 | EmployeeId = lookup.GetEmployeeId(saleInfo.Employee), 45 | ProductId = lookup.GetProductIdByName(saleInfo.Product), 46 | Quantity = saleInfo.Quantity 47 | }; 48 | } 49 | 50 | [When(@"I create a sale")] 51 | public void WhenICreateASale() 52 | { 53 | var command = _context.Container 54 | .GetService(); 55 | 56 | command.Execute(_model); 57 | } 58 | 59 | [Then(@"the following sales record should be recorded:")] 60 | public void ThenTheFollowingSalesRecordShouldBeRecorded(Table table) 61 | { 62 | var saleRecord = table.CreateInstance(); 63 | 64 | var database = _context.DatabaseService; 65 | 66 | var sale = database.Sales.Last(); 67 | 68 | var lookup = new DatabaseLookup(database); 69 | 70 | var customerId = lookup.GetCustomerId(saleRecord.Customer); 71 | 72 | var employeeId = lookup.GetEmployeeId(saleRecord.Employee); 73 | 74 | var productId = lookup.GetProductIdByName(saleRecord.Product); 75 | 76 | Assert.That(sale.Date, 77 | Is.EqualTo(saleRecord.Date)); 78 | 79 | Assert.That(sale.Customer.Id, 80 | Is.EqualTo(customerId)); 81 | 82 | Assert.That(sale.Employee.Id, 83 | Is.EqualTo(employeeId)); 84 | 85 | Assert.That(sale.Product.Id, 86 | Is.EqualTo(productId)); 87 | 88 | Assert.That(sale.UnitPrice, 89 | Is.EqualTo(saleRecord.UnitPrice)); 90 | 91 | Assert.That(sale.Quantity, 92 | Is.EqualTo(saleRecord.Quantity)); 93 | 94 | Assert.That(sale.TotalPrice, 95 | Is.EqualTo(saleRecord.TotalPrice)); 96 | } 97 | 98 | [Then(@"the following sale-occurred notification should be sent to the inventory system:")] 99 | public void ThenTheFollowingNotificationThatASaleOccurredShouldBeSentToTheInventorySystem(Table table) 100 | { 101 | var notification = table.CreateInstance(); 102 | 103 | var mockInventoryClient = _context.Mocker.GetMock(); 104 | 105 | mockInventoryClient.Verify(p => p.NotifySaleOccurred( 106 | notification.ProductId, 107 | notification.Quantity), 108 | Times.Once); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Specification/Sales/CreateASale/CreateSaleInfoModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Specification.Sales.CreateASale 6 | { 7 | public class CreateSaleInfoModel 8 | { 9 | public DateTime Date { get; set; } 10 | 11 | public string Customer { get; set; } = string.Empty; 12 | 13 | public string Employee { get; set; } = string.Empty; 14 | 15 | public string Product { get; set; } = string.Empty; 16 | 17 | public int Quantity { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Specification/Sales/CreateASale/CreateSaleOccurredNotificationModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Specification.Sales.CreateASale 6 | { 7 | public class CreateSaleOccurredNotificationModel 8 | { 9 | public int ProductId { get; set; } 10 | 11 | public int Quantity { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Specification/Sales/CreateASale/CreateSaleRecordModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Specification.Sales.CreateASale 6 | { 7 | public class CreateSaleRecordModel 8 | { 9 | public DateTime Date { get; set; } 10 | 11 | public string Customer { get; set; } = string.Empty; 12 | 13 | public string Employee { get; set; } = string.Empty; 14 | 15 | public string Product { get; set; } = string.Empty; 16 | 17 | public decimal UnitPrice { get; set; } 18 | 19 | public int Quantity { get; set; } 20 | 21 | public decimal TotalPrice { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Specification/Sales/GetSaleDetails/GetSaleDetails.feature: -------------------------------------------------------------------------------- 1 | Feature: Get Sale Details 2 | As a sales person 3 | I want to get the details of a sale 4 | So that I can review the sale 5 | 6 | Scenario: Get Sale Details 7 | When I request the sale details for sale 1 8 | Then the following sale details should be returned: 9 | | Id | Date | Customer | Employee | Product | Unit Price | Quantity | Total Price | 10 | | 1 | 2022-01-01 | Martin Fowler | Eric Evans | Spaghetti | 5.00 | 1 | 5.00 | 11 | -------------------------------------------------------------------------------- /Specification/Sales/GetSaleDetails/GetSaleDetails.feature.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by SpecFlow (https://www.specflow.org/). 4 | // SpecFlow Version:3.9.0.0 5 | // SpecFlow Generator Version:3.9.0.0 6 | // 7 | // Changes to this file may cause incorrect behavior and will be lost if 8 | // the code is regenerated. 9 | // 10 | // ------------------------------------------------------------------------------ 11 | #region Designer generated code 12 | #pragma warning disable 13 | namespace CleanArchitecture.Specification.Sales.GetSaleDetails 14 | { 15 | using TechTalk.SpecFlow; 16 | using System; 17 | using System.Linq; 18 | 19 | 20 | [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] 21 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 22 | [NUnit.Framework.TestFixtureAttribute()] 23 | [NUnit.Framework.DescriptionAttribute("Get Sale Details")] 24 | public partial class GetSaleDetailsFeature 25 | { 26 | 27 | private TechTalk.SpecFlow.ITestRunner testRunner; 28 | 29 | private string[] _featureTags = ((string[])(null)); 30 | 31 | #line 1 "GetSaleDetails.feature" 32 | #line hidden 33 | 34 | [NUnit.Framework.OneTimeSetUpAttribute()] 35 | public virtual void FeatureSetup() 36 | { 37 | testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); 38 | TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Sales/GetSaleDetails", "Get Sale Details", "\tAs a sales person\r\n\tI want to get the details of a sale\r\n\tSo that I can review t" + 39 | "he sale", ProgrammingLanguage.CSharp, ((string[])(null))); 40 | testRunner.OnFeatureStart(featureInfo); 41 | } 42 | 43 | [NUnit.Framework.OneTimeTearDownAttribute()] 44 | public virtual void FeatureTearDown() 45 | { 46 | testRunner.OnFeatureEnd(); 47 | testRunner = null; 48 | } 49 | 50 | [NUnit.Framework.SetUpAttribute()] 51 | public virtual void TestInitialize() 52 | { 53 | } 54 | 55 | [NUnit.Framework.TearDownAttribute()] 56 | public virtual void TestTearDown() 57 | { 58 | testRunner.OnScenarioEnd(); 59 | } 60 | 61 | public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) 62 | { 63 | testRunner.OnScenarioInitialize(scenarioInfo); 64 | testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); 65 | } 66 | 67 | public virtual void ScenarioStart() 68 | { 69 | testRunner.OnScenarioStart(); 70 | } 71 | 72 | public virtual void ScenarioCleanup() 73 | { 74 | testRunner.CollectScenarioErrors(); 75 | } 76 | 77 | [NUnit.Framework.TestAttribute()] 78 | [NUnit.Framework.DescriptionAttribute("Get Sale Details")] 79 | public virtual void GetSaleDetails() 80 | { 81 | string[] tagsOfScenario = ((string[])(null)); 82 | System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); 83 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Get Sale Details", null, tagsOfScenario, argumentsOfScenario, this._featureTags); 84 | #line 6 85 | this.ScenarioInitialize(scenarioInfo); 86 | #line hidden 87 | bool isScenarioIgnored = default(bool); 88 | bool isFeatureIgnored = default(bool); 89 | if ((tagsOfScenario != null)) 90 | { 91 | isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 92 | } 93 | if ((this._featureTags != null)) 94 | { 95 | isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 96 | } 97 | if ((isScenarioIgnored || isFeatureIgnored)) 98 | { 99 | testRunner.SkipScenario(); 100 | } 101 | else 102 | { 103 | this.ScenarioStart(); 104 | #line 7 105 | testRunner.When("I request the sale details for sale 1", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 106 | #line hidden 107 | TechTalk.SpecFlow.Table table7 = new TechTalk.SpecFlow.Table(new string[] { 108 | "Id", 109 | "Date", 110 | "Customer", 111 | "Employee", 112 | "Product", 113 | "Unit Price", 114 | "Quantity", 115 | "Total Price"}); 116 | table7.AddRow(new string[] { 117 | "1", 118 | "2022-01-01", 119 | "Martin Fowler", 120 | "Eric Evans", 121 | "Spaghetti", 122 | "5.00", 123 | "1", 124 | "5.00"}); 125 | #line 8 126 | testRunner.Then("the following sale details should be returned:", ((string)(null)), table7, "Then "); 127 | #line hidden 128 | } 129 | this.ScenarioCleanup(); 130 | } 131 | } 132 | } 133 | #pragma warning restore 134 | #endregion 135 | -------------------------------------------------------------------------------- /Specification/Sales/GetSaleDetails/GetSaleDetailsModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Specification.Sales.GetSaleDetails 6 | { 7 | public class GetSaleDetailsModel 8 | { 9 | public int Id { get; set; } 10 | 11 | public DateTime Date { get; set; } 12 | 13 | public string Customer { get; set; } = string.Empty; 14 | 15 | public string Employee { get; set; } = string.Empty; 16 | 17 | public string Product { get; set; } = string.Empty; 18 | 19 | public decimal UnitPrice { get; set; } 20 | 21 | public int Quantity { get; set; } 22 | 23 | public decimal TotalPrice { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Specification/Sales/GetSaleDetails/GetSaleDetailsSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Sales.Queries.GetSaleDetail; 5 | using CleanArchitecture.Specification.Shared; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using NUnit.Framework; 8 | using TechTalk.SpecFlow; 9 | using TechTalk.SpecFlow.Assist; 10 | using AppContext = CleanArchitecture.Specification.Shared.AppContext; 11 | 12 | namespace CleanArchitecture.Specification.Sales.GetSaleDetails 13 | { 14 | [Binding] 15 | public class GetSaleDetailsSteps 16 | { 17 | private readonly AppContext _context; 18 | private SaleDetailModel _result; 19 | 20 | public GetSaleDetailsSteps(AppContext context) 21 | { 22 | _context = context; 23 | _result = new SaleDetailModel(); 24 | } 25 | 26 | [When(@"I request the sale details for sale (.*)")] 27 | public void WhenIRequestTheSaleDetailsForSale(int saleId) 28 | { 29 | var query = _context.Container 30 | .GetService(); 31 | 32 | _result = query.Execute(saleId); 33 | } 34 | 35 | [Then(@"the following sale details should be returned:")] 36 | public void ThenTheFollowingResultsShouldBeReturned(Table table) 37 | { 38 | var model = table.CreateInstance(); 39 | 40 | Assert.That(_result.Id, 41 | Is.EqualTo(model.Id)); 42 | 43 | Assert.That(_result.Date, 44 | Is.EqualTo(model.Date)); 45 | 46 | Assert.That(_result.CustomerName, 47 | Is.EqualTo(model.Customer)); 48 | 49 | Assert.That(_result.EmployeeName, 50 | Is.EqualTo(model.Employee)); 51 | 52 | Assert.That(_result.ProductName, 53 | Is.EqualTo(model.Product)); 54 | 55 | Assert.That(_result.UnitPrice, 56 | Is.EqualTo(model.UnitPrice)); 57 | 58 | Assert.That(_result.Quantity, 59 | Is.EqualTo(model.Quantity)); 60 | 61 | Assert.That(_result.TotalPrice, 62 | Is.EqualTo(model.TotalPrice)); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Specification/Sales/GetSalesList/GetSalesList.feature: -------------------------------------------------------------------------------- 1 | Feature: Get Sales List 2 | As a sales person 3 | I want to get a list of sales 4 | So I can find a sale to review 5 | 6 | @mytag 7 | Scenario: Get a List of Sales 8 | When I request a list of sales 9 | Then the following sales list should be returned: 10 | | Id | Date | Customer | Employee | Product | Unit Price | Quantity | Total Price | 11 | | 1 | 2022-01-01 | Martin Fowler | Eric Evans | Spaghetti | 5.00 | 1 | 5.00 | 12 | | 2 | 2022-01-02 | Uncle Bob | Greg Young | Lasagne | 10.00 | 2 | 20.00 | 13 | | 3 | 2022-01-03 | Kent Beck | Udi Dahan | Ravioli | 15.00 | 3 | 45.00 | -------------------------------------------------------------------------------- /Specification/Sales/GetSalesList/GetSalesList.feature.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by SpecFlow (https://www.specflow.org/). 4 | // SpecFlow Version:3.9.0.0 5 | // SpecFlow Generator Version:3.9.0.0 6 | // 7 | // Changes to this file may cause incorrect behavior and will be lost if 8 | // the code is regenerated. 9 | // 10 | // ------------------------------------------------------------------------------ 11 | #region Designer generated code 12 | #pragma warning disable 13 | namespace CleanArchitecture.Specification.Sales.GetSalesList 14 | { 15 | using TechTalk.SpecFlow; 16 | using System; 17 | using System.Linq; 18 | 19 | 20 | [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] 21 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 22 | [NUnit.Framework.TestFixtureAttribute()] 23 | [NUnit.Framework.DescriptionAttribute("Get Sales List")] 24 | public partial class GetSalesListFeature 25 | { 26 | 27 | private TechTalk.SpecFlow.ITestRunner testRunner; 28 | 29 | private string[] _featureTags = ((string[])(null)); 30 | 31 | #line 1 "GetSalesList.feature" 32 | #line hidden 33 | 34 | [NUnit.Framework.OneTimeSetUpAttribute()] 35 | public virtual void FeatureSetup() 36 | { 37 | testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); 38 | TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Sales/GetSalesList", "Get Sales List", "\tAs a sales person\r\n\tI want to get a list of sales\r\n\tSo I can find a sale to revi" + 39 | "ew", ProgrammingLanguage.CSharp, ((string[])(null))); 40 | testRunner.OnFeatureStart(featureInfo); 41 | } 42 | 43 | [NUnit.Framework.OneTimeTearDownAttribute()] 44 | public virtual void FeatureTearDown() 45 | { 46 | testRunner.OnFeatureEnd(); 47 | testRunner = null; 48 | } 49 | 50 | [NUnit.Framework.SetUpAttribute()] 51 | public virtual void TestInitialize() 52 | { 53 | } 54 | 55 | [NUnit.Framework.TearDownAttribute()] 56 | public virtual void TestTearDown() 57 | { 58 | testRunner.OnScenarioEnd(); 59 | } 60 | 61 | public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) 62 | { 63 | testRunner.OnScenarioInitialize(scenarioInfo); 64 | testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); 65 | } 66 | 67 | public virtual void ScenarioStart() 68 | { 69 | testRunner.OnScenarioStart(); 70 | } 71 | 72 | public virtual void ScenarioCleanup() 73 | { 74 | testRunner.CollectScenarioErrors(); 75 | } 76 | 77 | [NUnit.Framework.TestAttribute()] 78 | [NUnit.Framework.DescriptionAttribute("Get a List of Sales")] 79 | [NUnit.Framework.CategoryAttribute("mytag")] 80 | public virtual void GetAListOfSales() 81 | { 82 | string[] tagsOfScenario = new string[] { 83 | "mytag"}; 84 | System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); 85 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Get a List of Sales", null, tagsOfScenario, argumentsOfScenario, this._featureTags); 86 | #line 7 87 | this.ScenarioInitialize(scenarioInfo); 88 | #line hidden 89 | bool isScenarioIgnored = default(bool); 90 | bool isFeatureIgnored = default(bool); 91 | if ((tagsOfScenario != null)) 92 | { 93 | isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 94 | } 95 | if ((this._featureTags != null)) 96 | { 97 | isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); 98 | } 99 | if ((isScenarioIgnored || isFeatureIgnored)) 100 | { 101 | testRunner.SkipScenario(); 102 | } 103 | else 104 | { 105 | this.ScenarioStart(); 106 | #line 8 107 | testRunner.When("I request a list of sales", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); 108 | #line hidden 109 | TechTalk.SpecFlow.Table table8 = new TechTalk.SpecFlow.Table(new string[] { 110 | "Id", 111 | "Date", 112 | "Customer", 113 | "Employee", 114 | "Product", 115 | "Unit Price", 116 | "Quantity", 117 | "Total Price"}); 118 | table8.AddRow(new string[] { 119 | "1", 120 | "2022-01-01", 121 | "Martin Fowler", 122 | "Eric Evans", 123 | "Spaghetti", 124 | "5.00", 125 | "1", 126 | "5.00"}); 127 | table8.AddRow(new string[] { 128 | "2", 129 | "2022-01-02", 130 | "Uncle Bob", 131 | "Greg Young", 132 | "Lasagne", 133 | "10.00", 134 | "2", 135 | "20.00"}); 136 | table8.AddRow(new string[] { 137 | "3", 138 | "2022-01-03", 139 | "Kent Beck", 140 | "Udi Dahan", 141 | "Ravioli", 142 | "15.00", 143 | "3", 144 | "45.00"}); 145 | #line 9 146 | testRunner.Then("the following sales list should be returned:", ((string)(null)), table8, "Then "); 147 | #line hidden 148 | } 149 | this.ScenarioCleanup(); 150 | } 151 | } 152 | } 153 | #pragma warning restore 154 | #endregion 155 | -------------------------------------------------------------------------------- /Specification/Sales/GetSalesList/GetSalesListModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CleanArchitecture.Specification.Sales.GetSalesList 6 | { 7 | public class GetSalesListModel 8 | { 9 | public int Id { get; set; } 10 | 11 | public DateTime Date { get; set; } 12 | 13 | public string Customer { get; set; } = string.Empty; 14 | 15 | public string Employee { get; set; } = string.Empty; 16 | 17 | public string Product { get; set; } = string.Empty; 18 | 19 | public decimal UnitPrice { get; set; } 20 | 21 | public int Quantity { get; set; } 22 | 23 | public decimal TotalPrice { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Specification/Sales/GetSalesList/GetSalesListSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Sales.Queries.GetSalesList; 5 | using CleanArchitecture.Specification.Shared; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using NUnit.Framework; 8 | using TechTalk.SpecFlow; 9 | using TechTalk.SpecFlow.Assist; 10 | using AppContext = CleanArchitecture.Specification.Shared.AppContext; 11 | 12 | namespace CleanArchitecture.Specification.Sales.GetSalesList 13 | { 14 | [Binding] 15 | public class GetSalesListSteps 16 | { 17 | private readonly AppContext _context; 18 | private List _results; 19 | 20 | public GetSalesListSteps(AppContext context) 21 | { 22 | _context = context; 23 | _results = new List(); 24 | } 25 | 26 | [When(@"I request a list of sales")] 27 | public void WhenIRequestAListOfSales() 28 | { 29 | var query = _context.Container 30 | .GetService(); 31 | 32 | _results = query.Execute(); 33 | } 34 | 35 | [Then(@"the following sales list should be returned:")] 36 | public void ThenTheFollowingSalesShouldBeReturned(Table table) 37 | { 38 | var models = table.CreateSet().ToList(); 39 | 40 | for (var i = 0; i < models.Count; i++) 41 | { 42 | var model = models[i]; 43 | 44 | var result = _results[i]; 45 | 46 | Assert.That(result.Id, 47 | Is.EqualTo(model.Id)); 48 | 49 | Assert.That(result.Date, 50 | Is.EqualTo(model.Date)); 51 | 52 | Assert.That(result.CustomerName, 53 | Is.EqualTo(model.Customer)); 54 | 55 | Assert.That(result.EmployeeName, 56 | Is.EqualTo(model.Employee)); 57 | 58 | Assert.That(result.ProductName, 59 | Is.EqualTo(model.Product)); 60 | 61 | Assert.That(result.UnitPrice, 62 | Is.EqualTo(model.UnitPrice)); 63 | 64 | Assert.That(result.Quantity, 65 | Is.EqualTo(model.Quantity)); 66 | 67 | Assert.That(result.TotalPrice, 68 | Is.EqualTo(model.TotalPrice)); 69 | 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Specification/Shared/AppContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Moq.AutoMock; 5 | using CleanArchitecture.Application.Interfaces; 6 | using CleanArchitecture.Common.Dates; 7 | using System.Runtime.Loader; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.EntityFrameworkCore; 10 | 11 | namespace CleanArchitecture.Specification.Shared 12 | { 13 | public class AppContext 14 | { 15 | public AutoMocker Mocker; 16 | public IServiceProvider Container; 17 | public IDatabaseService DatabaseService; 18 | public IInventoryService InventoryService; 19 | public IDateService DateService; 20 | 21 | public AppContext() 22 | { 23 | Mocker = new AutoMocker(); 24 | 25 | var options = new DbContextOptionsBuilder() 26 | .UseInMemoryDatabase(databaseName: "CleanArchitectureInMemory") 27 | .Options; 28 | 29 | DatabaseService = new MockDatabaseService(options); 30 | 31 | InventoryService = Mocker.GetMock().Object; 32 | 33 | var mockDateService = Mocker.GetMock(); 34 | 35 | mockDateService 36 | .Setup(p => p.GetDate()) 37 | .Returns(DateTime.Parse("2001-02-03")); 38 | 39 | DateService = mockDateService.Object; 40 | 41 | var files = Directory.GetFiles( 42 | AppDomain.CurrentDomain.BaseDirectory, 43 | "CleanArchitecture*.dll"); 44 | 45 | var assemblies = files 46 | .Select(p => AssemblyLoadContext.Default.LoadFromAssemblyPath(p)); 47 | 48 | var provider = new ServiceCollection() 49 | .Scan(p => p.FromAssemblies(assemblies) 50 | .AddClasses() 51 | .AsMatchingInterface()) 52 | .AddSingleton(_ => DatabaseService) 53 | .AddSingleton(_ => InventoryService) 54 | .AddSingleton(_ => DateService) 55 | .BuildServiceProvider(); 56 | 57 | Container = provider; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Specification/Shared/DatabaseLookup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CleanArchitecture.Application.Interfaces; 5 | 6 | namespace CleanArchitecture.Specification.Shared 7 | { 8 | public class DatabaseLookup 9 | { 10 | private readonly IDatabaseService _database; 11 | 12 | public DatabaseLookup(IDatabaseService database) 13 | { 14 | _database = database; 15 | } 16 | 17 | public int GetCustomerId(string name) 18 | { 19 | return _database.Customers 20 | .Single(p => p.Name == name).Id; 21 | } 22 | 23 | public int GetEmployeeId(string name) 24 | { 25 | return _database.Employees 26 | .Single(p => p.Name == name).Id; 27 | } 28 | 29 | public int GetProductIdByName(string name) 30 | { 31 | return _database.Products 32 | .Single(p => p.Name == name).Id; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Specification/Shared/MockDatabaseService.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitecture.Application.Interfaces; 2 | using CleanArchitecture.Domain.Customers; 3 | using CleanArchitecture.Domain.Employees; 4 | using CleanArchitecture.Domain.Products; 5 | using CleanArchitecture.Domain.Sales; 6 | using CleanArchitecture.Persistence.Customers; 7 | using CleanArchitecture.Persistence.Employees; 8 | using CleanArchitecture.Persistence.Products; 9 | using CleanArchitecture.Persistence.Sales; 10 | using Microsoft.EntityFrameworkCore; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Text; 15 | using System.Threading.Tasks; 16 | 17 | namespace CleanArchitecture.Specification.Shared 18 | { 19 | public class MockDatabaseService : DbContext, IDatabaseService 20 | { 21 | public MockDatabaseService(DbContextOptions options) : base(options) 22 | { 23 | Database.EnsureDeleted(); 24 | Database.EnsureCreated(); 25 | } 26 | 27 | public DbSet Customers { get; set; } 28 | 29 | public DbSet Employees { get; set; } 30 | 31 | public DbSet Products { get; set; } 32 | 33 | public DbSet Sales { get; set; } 34 | 35 | public void Save() 36 | { 37 | this.SaveChanges(); 38 | } 39 | 40 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 41 | { 42 | optionsBuilder.UseInMemoryDatabase(databaseName: "CleanArchitectureInMemory"); 43 | } 44 | 45 | protected override void OnModelCreating(ModelBuilder builder) 46 | { 47 | base.OnModelCreating(builder); 48 | 49 | new CustomerConfiguration().Configure(builder.Entity()); 50 | new EmployeeConfiguration().Configure(builder.Entity()); 51 | new ProductConfiguration().Configure(builder.Entity()); 52 | new SaleConfiguration().Configure(builder.Entity()); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Specification/Specification.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | disable 6 | enable 7 | CleanArchitecture.$(MSBuildProjectName) 8 | CleanArchitecture.$(MSBuildProjectName.Replace(" ", "_")) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | --------------------------------------------------------------------------------