master d985326b38dd cached
89 files
113.0 KB
26.6k tokens
271 symbols
1 requests
Download .txt
Repository: CanerPatir/aspnet-core-clean-arch
Branch: master
Commit: d985326b38dd
Files: 89
Total size: 113.0 KB

Directory structure:
gitextract_22c5k2bq/

├── .gitignore
├── Directory.build.props
├── LICENSE
├── README.md
├── aspnet-core-clean-arch.sln
├── src/
│   ├── Application/
│   │   ├── Application.csproj
│   │   ├── ICommandHandler.cs
│   │   ├── IEventHandler.cs
│   │   ├── IQueryHandler.cs
│   │   └── UseCases/
│   │       ├── AddContentToProduct/
│   │       │   ├── AddContentToProductCommand.cs
│   │       │   └── AddContentToProductCommandHandler.cs
│   │       └── CreateProduct/
│   │           ├── CreateProductCommand.cs
│   │           └── CreateProductCommandHandler.cs
│   ├── Domain/
│   │   ├── Abstraction/
│   │   │   ├── AggregateRoot.cs
│   │   │   ├── BaseDomainEvent.cs
│   │   │   ├── Entity.cs
│   │   │   ├── IRepository.cs
│   │   │   ├── InstanceEventRouter.cs
│   │   │   └── ValueObject.cs
│   │   ├── BusinessException.cs
│   │   ├── Domain.csproj
│   │   └── ProductContext/
│   │       ├── AttributeRef.cs
│   │       ├── BrandRef.cs
│   │       ├── CategoryRef.cs
│   │       ├── Content.cs
│   │       ├── Events/
│   │       │   ├── AttributeAddedToProduct.cs
│   │       │   ├── BaseProductEvent.cs
│   │       │   ├── ContentAddedToProduct.cs
│   │       │   ├── ImageAddedToProduct.cs
│   │       │   ├── ProductApproved.cs
│   │       │   ├── ProductCreated.cs
│   │       │   └── VariantAddedToProduct.cs
│   │       ├── IProductRepository.cs
│   │       ├── ImageRef.cs
│   │       ├── Product.cs
│   │       └── Variant.cs
│   ├── Infrastructure/
│   │   ├── Infrastructure.csproj
│   │   ├── Logging/
│   │   │   └── WebHostExtensions.cs
│   │   ├── Messaging/
│   │   │   └── Mediator/
│   │   │       ├── IMediator.cs
│   │   │       ├── Mediator.cs
│   │   │       └── ServiceCollectionExtensions.cs
│   │   ├── Persistence/
│   │   │   ├── AggregateNotFoundException.cs
│   │   │   ├── ConcurrencyException.cs
│   │   │   ├── EventStoreProductRepository.cs
│   │   │   ├── IEventStore.cs
│   │   │   ├── InMemory/
│   │   │   │   ├── EventStore/
│   │   │   │   │   └── InMemoryEventStore.cs
│   │   │   │   └── ServiceCollectionExtensions.cs
│   │   │   └── Mongo/
│   │   │       ├── EventStore/
│   │   │       │   └── MongoDbEventStore.cs
│   │   │       ├── IMongoDbContext.cs
│   │   │       ├── MongoDbContext.cs
│   │   │       ├── MongoDbProductRepository.cs
│   │   │       ├── MongoDbProvider.cs
│   │   │       ├── MongoDbRepository.cs
│   │   │       ├── MongoSettings.cs
│   │   │       └── ServiceCollectionExtensions.cs
│   │   └── ServiceCollectionExtensions.cs
│   └── WebApi/
│       ├── Extensions/
│       │   ├── ApplicationBuilderExtensions.cs
│       │   ├── OriginalNameCamelCaseContractResolver.cs
│       │   └── ServiceCollectionExtensions.cs
│       ├── Program.cs
│       ├── Properties/
│       │   └── launchSettings.json
│       ├── Startup.cs
│       ├── UseCases/
│       │   ├── AddContentToProduct/
│       │   │   ├── AddContentToProductCommandValidator.cs
│       │   │   └── ProductController.cs
│       │   └── CreateProduct/
│       │       ├── CreateProductCommandValidator.cs
│       │       └── ProductsController.cs
│       ├── WebApi.csproj
│       ├── app.config
│       ├── appsettings.Development.json
│       ├── appsettings.Production.json
│       ├── appsettings.Staging.json
│       └── appsettings.json
└── test/
    ├── Application.Tests/
    │   ├── Application.Tests.csproj
    │   ├── CommandHandlerTestBase.cs
    │   └── CreateProductCommandHandlerTests.cs
    ├── Domain.Specs/
    │   ├── Domain.Specs.csproj
    │   └── ProductSpecs/
    │       ├── WhenContentAddedToProduct.cs
    │       ├── WhenCreated.cs
    │       ├── WhenProductApproved.cs
    │       └── WhenVariantAddedToProduct.cs
    ├── Infrastructure.Tests/
    │   ├── Infrastructure.Tests.csproj
    │   └── MediatorTests.cs
    └── TestBase/
        ├── InMemoryDatabaseFixture.cs
        ├── ScenarioFor.cs
        ├── ScenarioForExisting.cs
        ├── SpecBase.cs
        ├── TestBase.csproj
        ├── TestBaseWithInMemoryDb.cs
        └── TestBaseWithIoC.cs

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
 Common IntelliJ Platform excludes

# User specific

.idea/

**/.idea/**/workspace.xml
**/.idea/**/tasks.xml
**/.idea/shelf/*
**/.idea/dictionaries

# Sensitive or high-churn files
**/.idea/**/dataSources/
**/.idea/**/dataSources.ids
**/.idea/**/dataSources.xml
**/.idea/**/dataSources.local.xml
**/.idea/**/sqlDataSources.xml
**/.idea/**/dynamic.xml

# Rider
# Rider auto-generates .iml files, and contentModel.xml
**/.idea/**/*.iml
**/.idea/**/contentModel.xml
**/.idea/**/modules.xml

*.suo
*.user
.vs/
[Bb]in/
[Oo]bj/
_UpgradeReport_Files/
[Pp]ackages/

Thumbs.db
Desktop.ini
.DS_Store

================================================
FILE: Directory.build.props
================================================
<Project>
    <PropertyGroup>
        <Version>0.0.1</Version>
		<LangVersion>latest</LangVersion>
    </PropertyGroup>
</Project>


================================================
FILE: LICENSE
================================================
    Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

================================================
FILE: README.md
================================================
## Experimental Product Domain Based On Hexagonal Architecture Principles
This project is a sample application built using .NET Core. The main goal of this project is implementing and better understanding DDD and hexagonal architecture principles.  
# Also known as
* Ports and Adapters
* Clean Architecture
* Onion Architecture
# Hexagonal architecture
![hexagonal](/hexagonal.png?raw=true "hexagonal")



With hexagonal architecture
* Domain layer contains enterprise wide logic and types and does not depend anything except these 
* Application layer contains business logic and types
* Infrastructure layer (including persistence, messaging, logging, presentation) contains all external concerns
* Presentation and Infrastructure layers depend only on Application
* Infrastructure dependencies can be replaced
with minimal effort. For instance, we can switch data store without touching business code. 

## Getting Started
Use these instructions to get the project up and running.

### Prerequisites
You will need the following tools:

* [Visual Studio Code or 2019](https://www.visualstudio.com/downloads/) or [JetBrains Rider](https://www.jetbrains.com/rider/download)
* [.NET Core SDK 3.0](https://www.microsoft.com/net/download/dotnet-core/3.0)

### Setup
Follow these steps to get your development environment set up:

  1. Clone the repository
  2. At the root directory, restore dependencies
     ```
     dotnet restore
     ```
  3. Build the solution
     ```
     dotnet build
     ```
  5. Run tests
     ```
     dotnet test
	 ```
## Technologies
* .NET Core 3.0
* ASP.NET Core 3.0
## License
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/CanerPatir/aspnet-core-clean-arch/blob/master/LICENSE) file for details.
## References
* https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/
* https://blog.ploeh.dk/2013/12/03/layers-onions-ports-adapters-its-all-the-same/

================================================
FILE: aspnet-core-clean-arch.sln
================================================

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{447CDAA9-FE29-40B2-AD2E-5B2AC8284965}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3E7CC539-9831-4E29-A871-B5A817584CCC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "src\Domain\Domain.csproj", "{32905C1D-DCE3-439F-B9A9-CB5765934008}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{8DD844E7-73F5-4EED-A37A-D431303D0FD7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi", "src\WebApi\WebApi.csproj", "{A6F7C54C-494B-44C7-86F0-D73FBC2DBF2B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".solutionItems", ".solutionItems", "{EE69A4C6-E7A9-4B6D-9B2F-CAFB23BDF6DF}"
ProjectSection(SolutionItems) = preProject
	Directory.build.props = Directory.build.props
	README.md = README.md
	LICENSE = LICENSE
	hexagonal.png = hexagonal.png
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestBase", "test\TestBase\TestBase.csproj", "{87282F82-F2FE-497E-BC35-2ACEEF151153}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "src\Application\Application.csproj", "{B31F4D82-5454-4E30-9FD6-F520E144FEAC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain.Specs", "test\Domain.Specs\Domain.Specs.csproj", "{CCEC4F14-D4A7-42A6-839D-A3676AED138F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application.Tests", "test\Application.Tests\Application.Tests.csproj", "{70A1D656-D4C6-40D8-9622-CB18313DACDA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.Tests", "test\Infrastructure.Tests\Infrastructure.Tests.csproj", "{8C6E9314-A891-42F8-A516-180EE0132175}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{32905C1D-DCE3-439F-B9A9-CB5765934008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{32905C1D-DCE3-439F-B9A9-CB5765934008}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{32905C1D-DCE3-439F-B9A9-CB5765934008}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{32905C1D-DCE3-439F-B9A9-CB5765934008}.Release|Any CPU.Build.0 = Release|Any CPU
		{8DD844E7-73F5-4EED-A37A-D431303D0FD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{8DD844E7-73F5-4EED-A37A-D431303D0FD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{8DD844E7-73F5-4EED-A37A-D431303D0FD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{8DD844E7-73F5-4EED-A37A-D431303D0FD7}.Release|Any CPU.Build.0 = Release|Any CPU
		{A6F7C54C-494B-44C7-86F0-D73FBC2DBF2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A6F7C54C-494B-44C7-86F0-D73FBC2DBF2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A6F7C54C-494B-44C7-86F0-D73FBC2DBF2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A6F7C54C-494B-44C7-86F0-D73FBC2DBF2B}.Release|Any CPU.Build.0 = Release|Any CPU
		{87282F82-F2FE-497E-BC35-2ACEEF151153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{87282F82-F2FE-497E-BC35-2ACEEF151153}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{87282F82-F2FE-497E-BC35-2ACEEF151153}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{87282F82-F2FE-497E-BC35-2ACEEF151153}.Release|Any CPU.Build.0 = Release|Any CPU
		{B31F4D82-5454-4E30-9FD6-F520E144FEAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{B31F4D82-5454-4E30-9FD6-F520E144FEAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{B31F4D82-5454-4E30-9FD6-F520E144FEAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{B31F4D82-5454-4E30-9FD6-F520E144FEAC}.Release|Any CPU.Build.0 = Release|Any CPU
		{CCEC4F14-D4A7-42A6-839D-A3676AED138F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{CCEC4F14-D4A7-42A6-839D-A3676AED138F}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{CCEC4F14-D4A7-42A6-839D-A3676AED138F}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{CCEC4F14-D4A7-42A6-839D-A3676AED138F}.Release|Any CPU.Build.0 = Release|Any CPU
		{70A1D656-D4C6-40D8-9622-CB18313DACDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{70A1D656-D4C6-40D8-9622-CB18313DACDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{70A1D656-D4C6-40D8-9622-CB18313DACDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{70A1D656-D4C6-40D8-9622-CB18313DACDA}.Release|Any CPU.Build.0 = Release|Any CPU
		{8C6E9314-A891-42F8-A516-180EE0132175}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{8C6E9314-A891-42F8-A516-180EE0132175}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{8C6E9314-A891-42F8-A516-180EE0132175}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{8C6E9314-A891-42F8-A516-180EE0132175}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(NestedProjects) = preSolution
		{32905C1D-DCE3-439F-B9A9-CB5765934008} = {447CDAA9-FE29-40B2-AD2E-5B2AC8284965}
		{8DD844E7-73F5-4EED-A37A-D431303D0FD7} = {447CDAA9-FE29-40B2-AD2E-5B2AC8284965}
		{A6F7C54C-494B-44C7-86F0-D73FBC2DBF2B} = {447CDAA9-FE29-40B2-AD2E-5B2AC8284965}
		{87282F82-F2FE-497E-BC35-2ACEEF151153} = {3E7CC539-9831-4E29-A871-B5A817584CCC}
		{B31F4D82-5454-4E30-9FD6-F520E144FEAC} = {447CDAA9-FE29-40B2-AD2E-5B2AC8284965}
		{CCEC4F14-D4A7-42A6-839D-A3676AED138F} = {3E7CC539-9831-4E29-A871-B5A817584CCC}
		{70A1D656-D4C6-40D8-9622-CB18313DACDA} = {3E7CC539-9831-4E29-A871-B5A817584CCC}
		{8C6E9314-A891-42F8-A516-180EE0132175} = {3E7CC539-9831-4E29-A871-B5A817584CCC}
	EndGlobalSection
EndGlobal


================================================
FILE: src/Application/Application.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netstandard2.1</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
      <ProjectReference Include="..\Domain\Domain.csproj" />
    </ItemGroup>

    <ItemGroup>
      <PackageReference Include="FluentValidation.AspNetCore" Version="8.5.0" />
      <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.0.0" />
    </ItemGroup>

</Project>


================================================
FILE: src/Application/ICommandHandler.cs
================================================
using System.Threading;
using System.Threading.Tasks;

namespace Application
{
    public interface ICommandHandler<in TMessage>
    {
        Task Handle(TMessage message, CancellationToken cancellationToken);
    }
}

================================================
FILE: src/Application/IEventHandler.cs
================================================
using System.Threading;
using System.Threading.Tasks;

namespace Application
{
    public interface IEventHandler <in TEvent>
    {
        Task Handle(TEvent @event, CancellationToken cancellationToken);
    }
}

================================================
FILE: src/Application/IQueryHandler.cs
================================================
using System.Threading;
using System.Threading.Tasks;

namespace Application
{
    public interface IQueryHandler<in TMessage, TResult>
    {
        Task<TResult> Handle(TMessage message, CancellationToken cancellationToken);
    }
}

================================================
FILE: src/Application/UseCases/AddContentToProduct/AddContentToProductCommand.cs
================================================
using System;

namespace Application.UseCases.AddContentToProduct
{
    public class AddContentToProductCommand
    {
        public AddContentToProductCommand(Guid productId, string title, string description,
            int attributeId, int attributeValueId)
        {
            ProductId = productId;
            Title = title;
            Description = description;
            AttributeId = attributeId;
            AttributeValueId = attributeValueId;
        }

        public Guid ProductId { get; }
        public string Title { get; }
        public string Description { get; }
        public int AttributeId { get; }
        public int AttributeValueId { get; }
    }
}

================================================
FILE: src/Application/UseCases/AddContentToProduct/AddContentToProductCommandHandler.cs
================================================
using System.Threading;
using System.Threading.Tasks;
using Domain.ProductContext;

namespace Application.UseCases.AddContentToProduct
{
    public class AddContentToProductCommandHandler : ICommandHandler<AddContentToProductCommand>
    {
        private readonly IProductRepository _productRepository;

        public AddContentToProductCommandHandler(IProductRepository productRepository)
        {
            _productRepository = productRepository;
        }

        public async Task Handle(AddContentToProductCommand command, CancellationToken cancellationToken)
        {
            var aggregate = await _productRepository.Load(command.ProductId, cancellationToken);

            aggregate.AddContent(command.Title, command.Description,
                new AttributeRef(command.AttributeId, command.AttributeValueId));

            await _productRepository.Save(aggregate, cancellationToken);
        }
    }
}

================================================
FILE: src/Application/UseCases/CreateProduct/CreateProductCommand.cs
================================================
namespace Application.UseCases.CreateProduct
{
    public class CreateProductCommand
    {
        public CreateProductCommand(int categoryId, int brandId, string productCode)
        {
            CategoryId = categoryId;
            BrandId = brandId;
            ProductCode = productCode;
        }

        public int CategoryId { get; }
        public int BrandId { get; }
        public string ProductCode { get; }
        
    }
}

================================================
FILE: src/Application/UseCases/CreateProduct/CreateProductCommandHandler.cs
================================================
using System;
using System.Threading;
using System.Threading.Tasks;
using Domain.ProductContext;

namespace Application.UseCases.CreateProduct
{
    public class CreateProductCommandHandler : ICommandHandler<CreateProductCommand>
    {
        private readonly IProductRepository _productRepository;

        public CreateProductCommandHandler(IProductRepository productRepository)
        {
            _productRepository = productRepository;
        }

        public async Task Handle(CreateProductCommand command, CancellationToken cancellationToken)
        {
            var newProduct = Product.Create(Guid.NewGuid(), command.CategoryId, command.BrandId, command.ProductCode);
            
            await _productRepository.Save(newProduct, cancellationToken);
        }
    }
}

================================================
FILE: src/Domain/Abstraction/AggregateRoot.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Domain
{
    public interface IAggregateRoot
    {
        int Version { get; }
        bool HasChanges();
        IEnumerable<object> GetUncommittedChanges();
        void MarkChangesAsCommitted();
        void LoadsFromHistory(IEnumerable<object> history);
    }

    public abstract class AggregateRoot<TKey> : IAggregateRoot
    {
        private readonly List<object> _changes = new List<object>();
        private readonly InstanceEventRouter _router;

        public AggregateRoot()
        {
            _router = new InstanceEventRouter();
        }

        public virtual TKey Id { get; protected set; }

        public virtual int Version { get; protected set; }

        public bool HasChanges() => _changes.Any();

        public IEnumerable<object> GetUncommittedChanges()
        {
            return _changes;
        }

        public void MarkChangesAsCommitted()
        {
            _changes.Clear();
        }

        public void LoadsFromHistory(IEnumerable<object> history)
        {
            foreach (var e in history) ApplyChange(e, false);
        }

        protected void Register<TEvent>(Action<TEvent> handler)
        {
            if (handler == null) throw new ArgumentNullException(nameof(handler));
            _router.ConfigureRoute(handler);
        }

        protected void ApplyChange(object @event)
        {
            ApplyChange(@event, true);
        }

        protected void ApplyChange(object @event, bool isNew)
        {
            if (@event == null) throw new ArgumentNullException(nameof(@event));

            _router.Route(@event);
            if (isNew)
            {
                _changes.Add(@event);
                Version++;
            }
        }

        #region overrides

        /// <inheritdoc />
        /// <summary>
        ///     Checks if this entity is transient (it has not an Id).
        /// </summary>
        /// <returns>True, if this entity is transient</returns>
        public virtual bool IsTransient()
        {
            if (EqualityComparer<TKey>.Default.Equals(Id, default))
            {
                return true;
            }

            //Workaround for EF Core since it sets int/long to min value when attaching to dbcontext
            if (typeof(TKey) == typeof(int))
            {
                return Convert.ToInt32(Id) <= 0;
            }

            if (typeof(TKey) == typeof(long))
            {
                return Convert.ToInt64(Id) <= 0;
            }

            return false;
        }

        public override bool Equals(object obj)
        {
            if (!(obj is AggregateRoot<TKey>))
            {
                return false;
            }

            //Same instances must be considered as equal
            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            //Transient objects are not considered as equal
            var other = (AggregateRoot<TKey>) obj;
            if (IsTransient() && other.IsTransient())
            {
                return false;
            }

            //Must have a IS-A relation of types or must be same type
            var typeOfThis = GetType();
            var typeOfOther = other.GetType();
            if (!typeOfThis.GetTypeInfo().IsAssignableFrom(typeOfOther) &&
                !typeOfOther.GetTypeInfo().IsAssignableFrom(typeOfThis))
            {
                return false;
            }

            return Id.Equals(other.Id);
        }

        public override int GetHashCode() => Id.GetHashCode();

        public static bool operator ==(AggregateRoot<TKey> left, AggregateRoot<TKey> right)
        {
            if (Equals(left, null))
            {
                return Equals(right, null);
            }

            return left.Equals(right);
        }

        public static bool operator !=(AggregateRoot<TKey> left, AggregateRoot<TKey> right) =>
            !(left == right);

        #endregion

        #region rule helpers

        protected void Should(Func<bool> predicate)
        {
            Should(predicate());
        }

        protected void Should(Func<bool> predicate, string otherwiseMessage)
        {
            Should(predicate(), otherwiseMessage);
        }

        protected void Should(bool clause)
        {
            if (clause) return;
            throw new BusinessException();
        }

        protected void Should(bool clause, string otherwiseMessage)
        {
            if (clause) return;
            throw new BusinessException(otherwiseMessage);
        }

        #endregion
    }
}

================================================
FILE: src/Domain/Abstraction/BaseDomainEvent.cs
================================================
namespace Domain.Abstraction
{
    public class BaseDomainEvent
    {
        public int Version { get; set; }
    }
}

================================================
FILE: src/Domain/Abstraction/Entity.cs
================================================
using System;

namespace Domain
{
    public abstract class Entity 
    {
        private readonly InstanceEventRouter _router;

        protected Entity() => _router = new InstanceEventRouter();

        protected void Register<TEvent>(Action<TEvent> handler)
        {
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }

            _router.ConfigureRoute(handler);
        }

        public virtual void Route(object @event)
        {
            if (@event == null)
            {
                throw new ArgumentNullException(nameof(@event));
            }

            _router.Route(@event);
        }
    }
}

================================================
FILE: src/Domain/Abstraction/IRepository.cs
================================================
using System.Threading;
using System.Threading.Tasks;

namespace Domain.Abstraction
{
    /// <summary>
    /// Provides an abstraction for object sotrage and
    /// </summary>
    /// <typeparam name="T">Type of aggregate root</typeparam>
    /// <typeparam name="TKey">Type of aggregate root's key</typeparam>
    public interface IRepository<T, TKey> where T : AggregateRoot<TKey>
    {
        Task Save(T aggregate, CancellationToken cancellationToken);
        Task<T> Load(TKey id, CancellationToken cancellationToken);
    }
}

================================================
FILE: src/Domain/Abstraction/InstanceEventRouter.cs
================================================
using System;
using System.Collections.Generic;

namespace Domain
{
    /// <summary>
    ///     Routes an event to a configured state handler.
    /// </summary>
    internal class InstanceEventRouter
    {
        private readonly Dictionary<Type, Action<object>> _handlers;

        /// <summary>
        ///     Initializes a new instance of the <see cref="InstanceEventRouter" /> class.
        /// </summary>
        public InstanceEventRouter() => _handlers = new Dictionary<Type, Action<object>>();

        /// <summary>
        ///     Adds a route for the specified event type to the specified state handler.
        /// </summary>
        /// <param name="event">The event type the route is for.</param>
        /// <param name="handler">The state handler that should be invoked when an event of the specified type is routed.</param>
        /// <exception cref="ArgumentNullException">
        ///     Thrown when <paramref name="event" /> or <paramref name="handler" /> is
        ///     <c>null</c>.
        /// </exception>
        public void ConfigureRoute(Type @event, Action<object> handler)
        {
            if (@event == null)
            {
                throw new ArgumentNullException(nameof(@event));
            }

            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }

            _handlers.Add(@event, handler);
        }

        /// <summary>
        ///     Adds a route for the specified event type to the specified state handler.
        /// </summary>
        /// <typeparam name="TEvent">The event type the route is for.</typeparam>
        /// <param name="handler">The state handler that should be invoked when an event of the specified type is routed.</param>
        /// <exception cref="T:System.ArgumentNullException">Thrown when <paramref name="handler" /> is <c>null</c>.</exception>
        public void ConfigureRoute<TEvent>(Action<TEvent> handler)
        {
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }

            _handlers.Add(typeof(TEvent), @event => handler((TEvent) @event));
        }

        /// <summary>
        ///     Routes the specified <paramref name="event" /> to a configured state handler, if any.
        /// </summary>
        /// <param name="event">The event to route.</param>
        /// <exception cref="T:System.ArgumentNullException">Thrown when the <paramref name="event" /> is null.</exception>
        public void Route(object @event)
        {
            if (@event == null)
            {
                throw new ArgumentNullException(nameof(@event));
            }

            if (_handlers.TryGetValue(@event.GetType(), out var handler))
            {
                handler(@event);
            }
        }
    }

}

================================================
FILE: src/Domain/Abstraction/ValueObject.cs
================================================
using System;
using System.Linq;

namespace Domain
{
      //Inspired from https://blogs.msdn.microsoft.com/cesardelatorre/2011/06/06/implementing-a-value-object-base-class-supertype-patternddd-patterns-related/
    /// <summary>
    ///     Base class for value objects.
    /// </summary>
    /// <typeparam name="TValueObject">The type of the value object.</typeparam>
    public abstract class ValueObject<TValueObject> : IEquatable<TValueObject>
        where TValueObject : ValueObject<TValueObject>
    {
        public bool Equals(TValueObject other)
        {
            if ((object) other == null)
            {
                return false;
            }

            var publicProperties = GetType().GetProperties();
            if (!publicProperties.Any())
            {
                return true;
            }

            return publicProperties.All(property =>
                Equals(property.GetValue(this, null), property.GetValue(other, null)));
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            var item = obj as ValueObject<TValueObject>;
            return (object) item != null && Equals((TValueObject) item);
        }

        public override int GetHashCode()
        {
            const int index = 1;
            const int initialHasCode = 31;

            var publicProperties = GetType().GetProperties();

            if (!publicProperties.Any())
            {
                return initialHasCode;
            }

            var hashCode = initialHasCode;
            var changeMultiplier = false;

            foreach (var property in publicProperties)
            {
                var value = property.GetValue(this, null);

                if (value == null)
                {
                    //support {"a",null,null,"a"} != {null,"a","a",null}
                    hashCode = hashCode ^ (index * 13);
                    continue;
                }

                hashCode = hashCode * (changeMultiplier ? 59 : 114) + value.GetHashCode();
                changeMultiplier = !changeMultiplier;
            }

            return hashCode;
        }

        public static bool operator ==(ValueObject<TValueObject> x, ValueObject<TValueObject> y)
        {
            if (ReferenceEquals(x, y))
            {
                return true;
            }

            if ((object) x == null || (object) y == null)
            {
                return false;
            }

            return x.Equals(y);
        }

        public static bool operator !=(ValueObject<TValueObject> x, ValueObject<TValueObject> y) => !(x == y);
    }
}

================================================
FILE: src/Domain/BusinessException.cs
================================================
using System;

namespace Domain
{
    public class BusinessException : Exception
    {
        public BusinessException()
        {
        }

        public BusinessException(string message) : base(message)
        {
        }
        
        public BusinessException(string message, Exception innerException) : base(message, innerException)
        {
        }
    }
}

================================================
FILE: src/Domain/Domain.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netstandard2.1</TargetFramework>
    </PropertyGroup>

</Project>


================================================
FILE: src/Domain/ProductContext/AttributeRef.cs
================================================
namespace Domain.ProductContext
{
    public class AttributeRef : ValueObject<AttributeRef>
    {
        public AttributeRef(int attributeId, int attributeValueId)
        {
            AttributeId = attributeId;
            AttributeValueId = attributeValueId;
        }

        /// <summary>
        /// Represents attribute for example: color, size, material etc.
        /// </summary>
        public int AttributeId { get; }

        /// <summary>
        /// Represents value of attribute for example: Red, Small, Cotton etc. 
        /// </summary>
        public int AttributeValueId { get; }

    }
}

================================================
FILE: src/Domain/ProductContext/BrandRef.cs
================================================
namespace Domain.ProductContext
{
    public class BrandRef : ValueObject<BrandRef>
    {
        public BrandRef(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; }

        public string Name { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/CategoryRef.cs
================================================
namespace Domain.ProductContext
{
    public class CategoryRef : ValueObject<CategoryRef>
    {
        public CategoryRef(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; }

        public string Name { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/Content.cs
================================================
using System.Collections.Generic;
using System.Linq;
using Domain.ProductContext.Events;

namespace Domain.ProductContext
{
    /// <summary>
    /// Represents group of product variant and can be projection of product detail page.  
    /// </summary>
    public class Content : Entity
    {
        private ICollection<Variant> _variants = new HashSet<Variant>();

        internal Content(string title, string description, AttributeRef slicerAttribute)
        {
            Title = title;
            Description = description;
            SlicerAttribute = slicerAttribute;
            
            Register<VariantAddedToProduct>(Apply);
        }

        private void Apply(VariantAddedToProduct @event)
        {
            _variants.Add(new Variant(@event.Barcode, @event.VarianterAttribute));
        }

        public IReadOnlyCollection<Variant> Variants => _variants.ToList();
        
        public string Title { get; private set; }

        public string Description { get; private set; }

        public AttributeRef SlicerAttribute { get; private set; }

        public bool HasSameTypeSlicerAttribute(AttributeRef attribute)
        {
            return SlicerAttribute.AttributeId == attribute.AttributeId;
        }

    }
}

================================================
FILE: src/Domain/ProductContext/Events/AttributeAddedToProduct.cs
================================================
using System;

namespace Domain.ProductContext.Events
{
    public class AttributeAddedToProduct : BaseProductEvent
    {
        public AttributeAddedToProduct(Guid productId, AttributeRef attribute)
        {
            ProductId = productId;
            Attribute = attribute;
        }

        public override Guid ProductId { get; }
        public AttributeRef Attribute { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/Events/BaseProductEvent.cs
================================================
using System;
using Domain.Abstraction;

namespace Domain.ProductContext.Events
{
    public abstract class BaseProductEvent : BaseDomainEvent
    {
        public abstract Guid ProductId { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/Events/ContentAddedToProduct.cs
================================================
using System;

namespace Domain.ProductContext.Events
{
    public class ContentAddedToProduct : BaseProductEvent
    {
        public ContentAddedToProduct(Guid productId, string title, string description, AttributeRef slicerAttribute)
        {
            ProductId = productId;
            Title = title;
            Description = description;
            SlicerAttribute = slicerAttribute;
        }

        public override Guid ProductId { get; }
        public string Title { get; }
        public string Description { get; }
        public AttributeRef SlicerAttribute { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/Events/ImageAddedToProduct.cs
================================================
using System;

namespace Domain.ProductContext.Events
{
    public class ImageAddedToProduct :BaseProductEvent
    {
        public ImageAddedToProduct(Guid productId, AttributeRef varianterAttr, ImageRef image)
        {
            ProductId = productId;
            VarianterAttr = varianterAttr;
            Image = image;
        }

        public override Guid ProductId { get; }
        public AttributeRef VarianterAttr { get; }
        public ImageRef Image { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/Events/ProductApproved.cs
================================================
using System;

namespace Domain.ProductContext.Events
{
    public class ProductApproved : BaseProductEvent
    {
        public ProductApproved(Guid productId)
        {
            ProductId = productId;
        }

        public override Guid ProductId { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/Events/ProductCreated.cs
================================================
using System;

namespace Domain.ProductContext.Events
{
    public class ProductCreated : BaseProductEvent
    {
        public ProductCreated(Guid productId, string productCode, int brandId, int categoryId)
        {
            ProductId = productId;
            BrandId = brandId;
            ProductCode = productCode;
            CategoryId = categoryId;
        }

        public override Guid ProductId { get; }
        public int BrandId { get; }
        public string ProductCode { get; }
        public int CategoryId { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/Events/VariantAddedToProduct.cs
================================================
using System;

namespace Domain.ProductContext.Events
{
    public class VariantAddedToProduct: BaseProductEvent
    {
        public VariantAddedToProduct(Guid productId, string barcode, AttributeRef slicerAttribute, AttributeRef varianterAttribute)
        {
            ProductId = productId;
            Barcode = barcode;
            SlicerAttribute = slicerAttribute;
            VarianterAttribute = varianterAttribute;
        }

        public override Guid ProductId { get; }
        public string Barcode { get; }
        public AttributeRef SlicerAttribute { get; }
        public AttributeRef VarianterAttribute { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/IProductRepository.cs
================================================
using System;
using Domain.Abstraction;

namespace Domain.ProductContext
{
    public interface IProductRepository : IRepository<Product, Guid>
    {
    }
}

================================================
FILE: src/Domain/ProductContext/ImageRef.cs
================================================
namespace Domain.ProductContext
{
    public class ImageRef : ValueObject<ImageRef>
    {
        public ImageRef(string relativeUrl, string relativeThumbUrl)
        {
            RelativeUrl = relativeUrl;
            RelativeThumbUrl = relativeThumbUrl;
        }

        public string RelativeUrl { get; }
        public string RelativeThumbUrl { get; }
    }
}

================================================
FILE: src/Domain/ProductContext/Product.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Domain.ProductContext.Events;

namespace Domain.ProductContext
{
    public class Product : AggregateRoot<Guid>
    {
        private readonly ICollection<AttributeRef> _attributes = new HashSet<AttributeRef>();

        private readonly ICollection<Content> _contents = new HashSet<Content>();

        public Product()
        {
            Register<ProductCreated>(Apply);
            Register<ContentAddedToProduct>(Apply);
            Register<AttributeAddedToProduct>(Apply);
            Register<VariantAddedToProduct>(Apply);
            Register<ProductApproved>(Apply);
            Register<ImageAddedToProduct>(Apply);
        }

        public static Product Create(Guid productId,
            int categoryId,
            int brandId,
            string productCode)
        {
            var product =  new Product();
            product.ApplyChange(
                new ProductCreated(productId,
                    productCode,
                    brandId, categoryId));

            return product;
        }

        public BrandRef Brand { get; private set; }

        public CategoryRef Category { get; private set; }

        public string Code { get; private set; }

        public IReadOnlyCollection<Content> Contents => _contents.ToList();

        public IReadOnlyCollection<AttributeRef> Attributes => _attributes.ToList();
        
        protected IReadOnlyCollection<Variant> Variants => _contents.SelectMany(c => c.Variants).ToList();
        
        protected IReadOnlyCollection<ImageRef> Images => Variants.SelectMany(c => c.Images).ToList();

        public bool IsApproved { get; private set; }
        
        private void Apply(ProductApproved @event) => IsApproved = true;

        private void Apply(ContentAddedToProduct @event) => _contents.Add(new Content(@event.Title, @event.Description, @event.SlicerAttribute));

        private void Apply(AttributeAddedToProduct @event) => _attributes.Add(@event.Attribute);

        private void Apply(VariantAddedToProduct @event) => _contents.First(c => c.SlicerAttribute == @event.SlicerAttribute).Route(@event);

        private void Apply(ProductCreated @event)
        {
            Id = @event.ProductId;
            Brand = new BrandRef(@event.BrandId, "");
            Category = new CategoryRef(@event.CategoryId, "");
            Code = @event.ProductCode;
        }
        
        private void Apply(ImageAddedToProduct @event)
        {
            var variant = _contents.SelectMany(c => c.Variants)
                .SingleOrDefault(v => v.VarianterAttribute == @event.VarianterAttr);
            
            // ReSharper disable once PossibleNullReferenceException
            variant.Route(@event);
        }
        
        public void AddContent(string title, string description, AttributeRef slicerAttribute)
        {
            if (_contents.Any())
            {
                Should(() => _contents.Any(c => c.HasSameTypeSlicerAttribute(slicerAttribute)),
                    "Given attribute type should belong to any content of product as slicer");
            }
            
            Should(() => _contents.All(c => c.SlicerAttribute != slicerAttribute),
                "Same content already exists with given attribute");

            ApplyChange(new ContentAddedToProduct(Id, title, description, slicerAttribute));
        }
        
        public void AddVariant(string barcode, AttributeRef slicerAttribute, AttributeRef varianterAttribute)
        {
            var content = _contents.SingleOrDefault(c => c.SlicerAttribute == slicerAttribute);
            Should(() => content != null, "No content found with given slicer attribute.");
            
            // ReSharper disable once PossibleNullReferenceException
            var variantsOfContent = content.Variants;

            if (variantsOfContent.Any())
            {
                Should(() =>  variantsOfContent.All(c => c.HasSameTypeVarianterAttribute(varianterAttribute)),
                    "Given attribute type should belong to any variant of product as varianter");
            }
            
            Should(() => variantsOfContent.All(v => v.VarianterAttribute != varianterAttribute) ,
                "Same variant already exists with given attribute");
            
            ApplyChange(new VariantAddedToProduct(Id, barcode, slicerAttribute, varianterAttribute));
        }

        public void AddAttributeToContent(AttributeRef attribute)
        {
            Should(() => _attributes.Any(a => a != attribute),
                "Given attribute had already been added to the product");

            ApplyChange(new AttributeAddedToProduct(Id, attribute));
        }
        
        public void Approve()
        {
            Should(() => _contents.Any(), "Product must have at least one content");
            Should(() => Variants.Any(), "Product must have at least one variant");
            Should(() => Variants.SelectMany(v => v.Images).Any(), "Product must have at least one image");
            
            ApplyChange(new ProductApproved(Id));
        } 
        
        public void AssignImage(ImageRef image, AttributeRef varianterAttr)
        {
            Should(() => Variants.Any(v => v.HasSameTypeVarianterAttribute(varianterAttr)),
                $"Product does not have any attribute with given attributeId: {varianterAttr.AttributeId}");
            
            ApplyChange(new ImageAddedToProduct(Id, varianterAttr, image));
        }
    }

}

================================================
FILE: src/Domain/ProductContext/Variant.cs
================================================
using System.Collections.Generic;
using System.Linq;
using Domain.ProductContext.Events;

namespace Domain.ProductContext
{
    public class Variant : Entity
    {
        private readonly ICollection<ImageRef> _images = new HashSet<ImageRef>();

        internal Variant(string barcode, AttributeRef varianterAttribute)
        {
            Barcode = barcode;
            VarianterAttribute = varianterAttribute;
            
            Register<ImageAddedToProduct>(Apply);
        }

        private void Apply(ImageAddedToProduct @event)
        {
            _images.Add(@event.Image);
        }

        public IReadOnlyCollection<ImageRef> Images => _images.ToList();
        
        public string Barcode { get; }

        public AttributeRef VarianterAttribute { get; }
        
        public bool HasSameTypeVarianterAttribute(AttributeRef attribute)
        {
            return VarianterAttribute.AttributeId == attribute.AttributeId;
        }
    }
}

================================================
FILE: src/Infrastructure/Infrastructure.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netstandard2.1</TargetFramework>
    </PropertyGroup>

    <ItemGroup>

         
        <PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" /> 
        
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
        <PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0" />
        <PackageReference Include="MongoDB.Driver" Version="2.9.2" />
        <PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.10.0" />
        <PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />


        <PackageReference Include="Serilog.AspNetCore" Version="3.0.0" />
        <PackageReference Include="Serilog.Sinks.ColoredConsole" Version="3.0.1" />
        <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
        <PackageReference Include="Serilog.Sinks.Graylog" Version="2.0.12" />
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\Application\Application.csproj" />
    </ItemGroup>

</Project>


================================================
FILE: src/Infrastructure/Logging/WebHostExtensions.cs
================================================
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.Graylog;
using Serilog.Sinks.Graylog.Core.Helpers;
using Serilog.Sinks.Graylog.Core.Transport;

namespace Infrastructure.Logging
{
    public static class WebHostExtensions
    {
        public static IWebHostBuilder ConfigureLoggingPlumbing(this IWebHostBuilder webHostBuilder)
        {
            return webHostBuilder.UseSerilog((context, loggerConfiguration) =>
            {
                var logLevel = context.Configuration.GetValue<LogEventLevel>("Serilog:MinimumLevel:Default");
                loggerConfiguration
                    .MinimumLevel.Verbose()
                    .Enrich.FromLogContext()
                    .Enrich.WithProperty("version",
                        Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>().Version)
                    .Enrich.WithProperty("env", context.HostingEnvironment.EnvironmentName)
                    .ReadFrom.Configuration(context.Configuration)
                    .WriteTo.Graylog(
                        new GraylogSinkOptions
                        {
                            HostnameOrAddress = context.Configuration["Serilog:GraylogIp"],
                            Port = int.Parse(context.Configuration["Serilog:GraylogPort"]),
                            TransportType = TransportType.Udp,
                            MinimumLogEventLevel = logLevel,
                            Facility = context.Configuration["Serilog:FacilityName"],
                            MessageGeneratorType = MessageIdGeneratortype.Timestamp
                        });

                if (context.Configuration.GetValue<bool>("Serilog:Console"))
                {
                    loggerConfiguration.WriteTo.ColoredConsole(logLevel,
                        "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}");
                }
            });
        }
    }
}

================================================
FILE: src/Infrastructure/Messaging/Mediator/IMediator.cs
================================================
using System.Threading;
using System.Threading.Tasks;

namespace Infrastructure.Messaging.Mediator
{
    public interface IMediator
    {
        Task SendAsync<TMessage>(TMessage message, CancellationToken cancellationToken = default);
        Task PublishAsync<TEvent>(TEvent message, CancellationToken cancellationToken = default);
        Task<TResult> SendAsync<TMessage, TResult>(TMessage @event, CancellationToken cancellationToken = default);
    }
}

================================================
FILE: src/Infrastructure/Messaging/Mediator/Mediator.cs
================================================
using System;
using System.Threading;
using System.Threading.Tasks;
using Application;
using Microsoft.Extensions.DependencyInjection;

namespace Infrastructure.Messaging.Mediator
{
    public class Mediator : IMediator
    {
        private readonly IServiceProvider _serviceProvider;

        public Mediator(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public Task SendAsync<TMessage>(TMessage message, CancellationToken cancellationToken)
        {
            var handler = _serviceProvider.GetRequiredService<ICommandHandler<TMessage>>();
            if (handler == null) throw new NullReferenceException($"handler of {nameof(TMessage)}");

            return handler.Handle(message, cancellationToken);
        }

        public Task PublishAsync<TEvent>(TEvent @event, CancellationToken cancellationToken = default)
        {
            var handler = _serviceProvider.GetRequiredService<IEventHandler<TEvent>>();
            if (handler == null) throw new NullReferenceException($"handler of {nameof(TEvent)}");

            return handler.Handle(@event, cancellationToken);        }

        public Task<TResult> SendAsync<TMessage, TResult>(TMessage message, CancellationToken cancellationToken)
        {
            var handler = _serviceProvider.GetRequiredService<IQueryHandler<TMessage, TResult>>();
            if (handler == null)
                throw new NullReferenceException($"handler of {nameof(TMessage)} which returning {nameof(TResult)}");

            return handler.Handle(message, cancellationToken);
        }
    }
}

================================================
FILE: src/Infrastructure/Messaging/Mediator/ServiceCollectionExtensions.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Application;
using Infrastructure.Messaging.Mediator;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddInProcessMessageBus(this IServiceCollection services)
        {
            services.AddSingleton<IMediator, Mediator>();

            var assembliesToScan = new[]
            {
                Assembly.GetCallingAssembly(),
                Assembly.GetEntryAssembly(),
                Assembly.GetExecutingAssembly()
            }.Distinct().ToArray();

            ConnectImplementationsToTypesClosing(typeof(ICommandHandler<>), services, assembliesToScan, false);
            ConnectImplementationsToTypesClosing(typeof(IQueryHandler<,>), services, assembliesToScan, false);

            var multiOpenInterfaces = new[]
            {
                typeof(ICommandHandler<>),
                typeof(IQueryHandler<,>)
            };

            foreach (var multiOpenInterface in multiOpenInterfaces)
            {
                var concretions = assembliesToScan
                    .SelectMany(a => a.DefinedTypes)
                    .Where(type => type.FindInterfacesThatClose(multiOpenInterface).Any())
                    .Where(type => type.IsConcrete() && type.IsOpenGeneric())
                    .ToList();

                foreach (var type in concretions)
                {
                    services.AddTransient(multiOpenInterface, type);
                }
            }

            return services;
        }


        /// <summary>
        /// Helper method use to differentiate behavior between request handlers and notification handlers.
        /// Request handlers should only be added once (so set addIfAlreadyExists to false)
        /// Notification handlers should all be added (set addIfAlreadyExists to true)
        /// </summary>
        private static void ConnectImplementationsToTypesClosing(Type openRequestInterface,
            IServiceCollection services,
            IEnumerable<Assembly> assembliesToScan,
            bool addIfAlreadyExists)
        {
            var concretions = new List<Type>();
            var interfaces = new List<Type>();
             
            foreach (var type in assembliesToScan.SelectMany(a => a.DefinedTypes).Where(t => !t.IsOpenGeneric()))
            {
                var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray();
                if (!interfaceTypes.Any()) continue;

                if (type.IsConcrete())
                {
                    concretions.Add(type);
                }

                foreach (var interfaceType in interfaceTypes)
                {
                    interfaces.Fill(interfaceType);
                }
            }

            foreach (var @interface in interfaces)
            {
                var exactMatches = concretions.Where(x => x.CanBeCastTo(@interface)).ToList();
                if (addIfAlreadyExists)
                {
                    foreach (var type in exactMatches)
                    {
                        services.AddTransient(@interface, type);
                    }
                }
                else
                {
                    if (exactMatches.Count > 1)
                    {
                        exactMatches.RemoveAll(m => !IsMatchingWithInterface(m, @interface));
                    }

                    foreach (var type in exactMatches)
                    {
                        services.TryAddTransient(@interface, type);
                    }
                }

                if (!@interface.IsOpenGeneric())
                {
                    AddConcretionsThatCouldBeClosed(@interface, concretions, services);
                }
            }
        }

        private static bool IsMatchingWithInterface(Type handlerType, Type handlerInterface)
        {
            if (handlerType == null || handlerInterface == null)
            {
                return false;
            }

            if (handlerType.IsInterface)
            {
                if (handlerType.GenericTypeArguments.SequenceEqual(handlerInterface.GenericTypeArguments))
                {
                    return true;
                }
            }
            else
            {
                return IsMatchingWithInterface(handlerType.GetInterface(handlerInterface.Name), handlerInterface);
            }

            return false;
        }

        private static void AddConcretionsThatCouldBeClosed(Type @interface, List<Type> concretions,
            IServiceCollection services)
        {
            foreach (var type in concretions
                .Where(x => x.IsOpenGeneric() && x.CouldCloseTo(@interface)))
            {
                try
                {
                    services.TryAddTransient(@interface, type.MakeGenericType(@interface.GenericTypeArguments));
                }
                catch (Exception)
                {
                    // ignored
                }
            }
        }

        private static bool CouldCloseTo(this Type openConcretion, Type closedInterface)
        {
            var openInterface = closedInterface.GetGenericTypeDefinition();
            var arguments = closedInterface.GenericTypeArguments;

            var concreteArguments = openConcretion.GenericTypeArguments;
            return arguments.Length == concreteArguments.Length && openConcretion.CanBeCastTo(openInterface);
        }

        private static bool CanBeCastTo(this Type pluggedType, Type pluginType)
        {
            if (pluggedType == null) return false;

            if (pluggedType == pluginType) return true;

            return pluginType.GetTypeInfo().IsAssignableFrom(pluggedType.GetTypeInfo());
        }

        private static IEnumerable<Type> FindInterfacesThatClose(this Type pluggedType, Type templateType)
        {
            return FindInterfacesThatClosesCore(pluggedType, templateType).Distinct();
        }


        private static IEnumerable<Type> FindInterfacesThatClosesCore(Type pluggedType, Type templateType)
        {
            if (pluggedType == null) yield break;

            if (!pluggedType.IsConcrete()) yield break;

            if (templateType.GetTypeInfo().IsInterface)
            {
                foreach (
                    var interfaceType in
                    pluggedType.GetInterfaces()
                        .Where(type =>
                            type.GetTypeInfo().IsGenericType && (type.GetGenericTypeDefinition() == templateType)))
                {
                    yield return interfaceType;
                }
            }
            else if (pluggedType.GetTypeInfo().BaseType.GetTypeInfo().IsGenericType &&
                     (pluggedType.GetTypeInfo().BaseType?.GetGenericTypeDefinition() == templateType))
            {
                yield return pluggedType.GetTypeInfo().BaseType;
            }

            if (pluggedType.GetTypeInfo().BaseType == typeof(object)) yield break;

            foreach (var interfaceType in FindInterfacesThatClosesCore(pluggedType.GetTypeInfo().BaseType, templateType)
            )
            {
                yield return interfaceType;
            }
        }

        private static bool IsConcrete(this Type type)
        {
            return !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().IsInterface;
        }

        private static bool IsOpenGeneric(this Type type)
        {
            return type.GetTypeInfo().IsGenericTypeDefinition || type.GetTypeInfo().ContainsGenericParameters;
        }

        private static void Fill<T>(this ICollection<T> list, T value)
        {
            if (list.Contains(value)) return;
            list.Add(value);
        }
    }
}

================================================
FILE: src/Infrastructure/Persistence/AggregateNotFoundException.cs
================================================
using System;

namespace Infrastructure.Persistence
{
    public class AggregateNotFoundException : Exception
    {
    }
}

================================================
FILE: src/Infrastructure/Persistence/ConcurrencyException.cs
================================================
using System;

namespace Infrastructure.Persistence
{
    public class ConcurrencyException : Exception
    {
        public ConcurrencyException()
        {
        }
        
        public ConcurrencyException(string message) : base(message)
        {
            
        }
    }
}

================================================
FILE: src/Infrastructure/Persistence/EventStoreProductRepository.cs
================================================
using System;
using System.Threading;
using System.Threading.Tasks;
using Domain.ProductContext;

namespace Infrastructure.Persistence
{
    internal class EventStoreProductRepository : IProductRepository
    {
        private readonly IEventStore _eventStore;
        
        public EventStoreProductRepository(IEventStore eventStore) => _eventStore = eventStore;

        public async Task Save(Product aggregate, CancellationToken cancellationToken)
        {
           await _eventStore.SaveEvents(aggregate.Id, aggregate.GetUncommittedChanges(), aggregate.Version);
           aggregate.MarkChangesAsCommitted();
        }

        public async Task<Product> Load(Guid id, CancellationToken cancellationToken)
        {
            var aggregate = new Product();//lots of ways to do this
            var e = await _eventStore.GetEventsForAggregate(id);
            aggregate.LoadsFromHistory(e);
            return aggregate;
        }
    }
}

================================================
FILE: src/Infrastructure/Persistence/IEventStore.cs
================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Infrastructure.Persistence
{
    public interface IEventStore
    {
        Task SaveEvents(Guid aggregateId, IEnumerable<object> events, int expectedVersion);
        Task<List<object>> GetEventsForAggregate(Guid aggregateId);
    }
}

================================================
FILE: src/Infrastructure/Persistence/InMemory/EventStore/InMemoryEventStore.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Infrastructure.Messaging.Mediator;

namespace Infrastructure.Persistence.InMemory.EventStore
{
    public class InMemoryEventStore : IEventStore
    {
        private readonly IMediator _publisher;

        private struct EventDescriptor
        {
            public readonly object EventData;
            public readonly Guid Id;
            public readonly int Version;

            public EventDescriptor(Guid id, object eventData, int version)
            {
                EventData = eventData;
                Version = version;
                Id = id;
            }
        }

        public InMemoryEventStore(IMediator publisher)
        {
            _publisher = publisher;
        }

        private readonly Dictionary<Guid, List<EventDescriptor>> _current =
            new Dictionary<Guid, List<EventDescriptor>>();

        public async Task SaveEvents(Guid aggregateId, IEnumerable<object> events, int expectedVersion)
        {
            List<EventDescriptor> eventDescriptors;

            // try to get event descriptors list for given aggregate id
            // otherwise -> create empty dictionary
            if (!_current.TryGetValue(aggregateId, out eventDescriptors))
            {
                eventDescriptors = new List<EventDescriptor>();
                _current.Add(aggregateId, eventDescriptors);
            }
            // check whether latest event version matches current aggregate version
            // otherwise -> throw exception
            else if (eventDescriptors[eventDescriptors.Count - 1].Version != expectedVersion && expectedVersion != -1)
            {
                throw new ConcurrencyException();
            }

            var i = expectedVersion;

            // iterate through current aggregate events increasing version with each processed event
            foreach (var @event in events)
            {
                i++;
//                @event.Version = i;

                // push event to the event descriptors list for current aggregate
                eventDescriptors.Add(new EventDescriptor(aggregateId, @event, i));

                // publish current event to the bus for further processing by subscribers
                await _publisher.PublishAsync(@event);
            }
        }

        // collect all processed events for given aggregate and return them as a list
        // used to build up an aggregate from its history (Domain.LoadsFromHistory)
        public Task<List<object>> GetEventsForAggregate(Guid aggregateId)
        {
            List<EventDescriptor> eventDescriptors;

            if (!_current.TryGetValue(aggregateId, out eventDescriptors))
            {
                throw new AggregateNotFoundException();
            }

            return Task.FromResult(eventDescriptors.Select(desc => desc.EventData).ToList());
        }
    }
}

================================================
FILE: src/Infrastructure/Persistence/InMemory/ServiceCollectionExtensions.cs
================================================
using Domain.ProductContext;
using Infrastructure.Persistence.InMemory.EventStore;
using Microsoft.Extensions.DependencyInjection;

namespace Infrastructure.Persistence.InMemory
{
    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddInMemoryEventStorePersistence(this IServiceCollection services)
        {
            return services
                .AddSingleton<IEventStore, InMemoryEventStore>()
                .AddScoped<IProductRepository, EventStoreProductRepository>();
        }
    }
}

================================================
FILE: src/Infrastructure/Persistence/Mongo/EventStore/MongoDbEventStore.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Infrastructure.Messaging.Mediator;
using MongoDB.Driver;

namespace Infrastructure.Persistence.Mongo.EventStore
{
    public class MongoDbEventStore : IEventStore
    {
        private readonly IMediator _publisher;
        private readonly IMongoCollection<EventDescriptor> _collection;


        public MongoDbEventStore(IMediator publisher, IMongoDbContext _mongoDbContext)
        {
            _publisher = publisher;
            _collection = _mongoDbContext.GetCollection<EventDescriptor>("events");
        }

        private class EventDescriptor
        {
            public EventDescriptor(Guid id, object eventData, int version)
            {
                EventData = eventData;
                Version = version;
                AggregateRootId = id;
                Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            }

            public long Timestamp { get; }
            
            public object EventData { get; }
            public int Version { get; }

            public Guid AggregateRootId { get; }
        }

        public async Task SaveEvents(Guid aggregateId, IEnumerable<object> events, int expectedVersion)
        {
            var last = _collection
                .AsQueryable()
                .Where(e => e.AggregateRootId == aggregateId)
                .OrderBy(e => e.Version)
                .ThenBy(e => e.Timestamp).LastOrDefault();
            
            if (last != null && last.Version != expectedVersion && expectedVersion != -1)
            {
                throw new ConcurrencyException();
            }

            var i = expectedVersion;
            var eventDescriptors = events.ToList().Select(@event =>
            {
                i++;
                return new EventDescriptor(aggregateId, @event, i);
            });

            await _collection.InsertManyAsync(eventDescriptors, new InsertManyOptions
            {
                IsOrdered = true,
                BypassDocumentValidation = false
            });

            await Task.WhenAll(events.Select(@event => _publisher.PublishAsync(@event)));
        }

        public Task<List<object>> GetEventsForAggregate(Guid aggregateId)
        {
            return Task.FromResult(_collection
                .AsQueryable()
                .Where(e => e.AggregateRootId == aggregateId)
                .OrderBy(e => e.Version)
                .ThenBy(e => e.Timestamp).ToList().Select(descriptor => descriptor.EventData).ToList());

        }
    }
}

================================================
FILE: src/Infrastructure/Persistence/Mongo/IMongoDbContext.cs
================================================
using MongoDB.Driver;

namespace Infrastructure.Persistence.Mongo
{
    public interface IMongoDbContext
    {
        IMongoCollection<T> GetCollection<T>(string collectionName);
    }
}

================================================
FILE: src/Infrastructure/Persistence/Mongo/MongoDbContext.cs
================================================
using MongoDB.Driver;

namespace Infrastructure.Persistence.Mongo
{

    public sealed class MongoDbContext : IMongoDbContext
    {
        private readonly IMongoDatabase _database;

        public MongoDbContext(IMongoDatabase database)
        {
            _database = database;
        }

        public IMongoCollection<T> GetCollection<T>(string collectionName)
        {
            return _database.GetCollection<T>(collectionName);
        }

    }
}

================================================
FILE: src/Infrastructure/Persistence/Mongo/MongoDbProductRepository.cs
================================================
using System;
using Domain.ProductContext;
using Infrastructure.Messaging.Mediator;

namespace Infrastructure.Persistence.Mongo
{
    public class MongoDbProductRepository : MongoDbRepository<Product, Guid>, IProductRepository
    {
        public MongoDbProductRepository(IMongoDbContext context, IMediator mediator) : base(mediator, context)
        {
        }

        protected override string CollectionName => "products";
    }
}

================================================
FILE: src/Infrastructure/Persistence/Mongo/MongoDbProvider.cs
================================================
using MongoDB.Driver;

namespace Infrastructure.Persistence.Mongo
{
    public class MongoDbProvider
    {
        public static IMongoDatabase Provide(MongoSettings settings)
        {
            //var client = new MongoClient(settings.Value.ConnectionString);

            MongoClientSettings clientSettings = new MongoClientSettings();
            clientSettings.Server = new MongoServerAddress(settings.Host, 10255);
            clientSettings.UseTls = settings.UseSsl;
            if (settings.UseSsl)
            {
                clientSettings.SslSettings = new SslSettings();
                clientSettings.SslSettings.EnabledSslProtocols = settings.EnabledSslProtocols;
            }

            MongoIdentity identity = new MongoInternalIdentity(settings.Database, settings.UserName);
            MongoIdentityEvidence evidence = new PasswordEvidence(settings.Password);

            clientSettings.Credential = new MongoCredential("SCRAM-SHA-1", identity, evidence);

            MongoClient client = new MongoClient(clientSettings);

            return client.GetDatabase(settings.Database);
        }
    }
}

================================================
FILE: src/Infrastructure/Persistence/Mongo/MongoDbRepository.cs
================================================
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Domain;
using Domain.Abstraction;
using Infrastructure.Messaging.Mediator;
using MongoDB.Driver;
using MongoDB.Driver.Linq;

namespace Infrastructure.Persistence.Mongo
{
    // todo: https://github.com/NEventStore/NEventStore.Persistence.MongoDB review the impl
    public abstract class MongoDbRepository<TAggregate, TKey> : IRepository<TAggregate, TKey>
        where TAggregate : AggregateRoot<TKey>
    {
        protected abstract string CollectionName { get; }

        private readonly IMediator _mediator;
        private readonly IMongoCollection<TAggregate> _collection;
        
        protected MongoDbRepository(IMediator mediator,IMongoDbContext context )
        {
            _mediator = mediator;
            _collection = context.GetCollection<TAggregate>(CollectionName);
        }
        
        public virtual async Task Save(TAggregate aggregate, CancellationToken cancellationToken)
        {
            var existing = await _collection.AsQueryable().Where(p => p.Id.Equals(aggregate.Id)).FirstOrDefaultAsync(cancellationToken);
            if (existing != null)
            {
                if (existing.Version > aggregate.Version)
                {
                    throw new ConcurrencyException();
                }

                await _collection.ReplaceOneAsync(c => c.Id.Equals(aggregate.Id), aggregate, new UpdateOptions()
                {
                    IsUpsert = true
                }, cancellationToken);
            }
            else
            {
                await _collection.InsertOneAsync(aggregate, new InsertOneOptions
                {
                    BypassDocumentValidation = false
                }, cancellationToken);
            }
            
            // todo: find a way to prevent inconsistency between state and domain events
            await DispatchDomainEventsAsync(aggregate, cancellationToken);
            
        }
        protected async Task DispatchDomainEventsAsync(TAggregate aggregateRoot, CancellationToken cancellationToken)
        {
            var domainEvents = aggregateRoot.GetUncommittedChanges();
            aggregateRoot.MarkChangesAsCommitted();
            
            await Task.WhenAll(domainEvents
                .Select(async domainEvent => await _mediator.PublishAsync(domainEvent, cancellationToken)));
        }
        
        public virtual Task<TAggregate> Load(TKey id, CancellationToken cancellationToken)
        {
            return _collection.AsQueryable().Where(p => p.Id.Equals(id)).FirstOrDefaultAsync(cancellationToken);
        }
    }
}

================================================
FILE: src/Infrastructure/Persistence/Mongo/MongoSettings.cs
================================================
using System.Security.Authentication;

namespace Infrastructure.Persistence.Mongo
{
    public class MongoSettings
    {
        //public string ConnectionString { get; set; }
        public int Port { get; set; } = 10255;
        public string Host { get; set; }
        public bool UseSsl { get; set; } = true;
        public string Database { get; set; }
        public SslProtocols EnabledSslProtocols { get; set; } = SslProtocols.Tls12;
        public string Password { get; internal set; }
        public string UserName { get; internal set; }
    }
}

================================================
FILE: src/Infrastructure/Persistence/Mongo/ServiceCollectionExtensions.cs
================================================
using Domain.ProductContext;
using Infrastructure.Persistence.Mongo.EventStore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Infrastructure.Persistence.Mongo
{
    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddMongoDbPersistence(this IServiceCollection services,
            IConfiguration configuration)
        {
            return services
                .Configure<MongoSettings>(configuration.GetSection("MongoDb"))
                .AddSingleton(c => MongoDbProvider.Provide(c.GetRequiredService<IOptions<MongoSettings>>().Value))
                .AddSingleton<IMongoDbContext, MongoDbContext>()
                .AddScoped<IProductRepository, MongoDbProductRepository>();
        }

        public static IServiceCollection AddMongoDbEventStorePersistence(this IServiceCollection services,
            IConfiguration configuration)
        {
            return services
                .Configure<MongoSettings>(configuration.GetSection("MongoDb"))
                .AddSingleton(c => MongoDbProvider.Provide(c.GetRequiredService<IOptions<MongoSettings>>().Value))
                .AddSingleton<IMongoDbContext, MongoDbContext>()
                .AddSingleton<IEventStore, MongoDbEventStore>()
                .AddScoped<IProductRepository, EventStoreProductRepository>();
        }
    }
}

================================================
FILE: src/Infrastructure/ServiceCollectionExtensions.cs
================================================
using Infrastructure.Persistence.InMemory;
using Infrastructure.Persistence.Mongo;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Infrastructure
{
    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddInfrastructure(this IServiceCollection services,
            IConfiguration configuration)
        {
            return services
                .AddInMemoryEventStorePersistence()
//                .AddMongoDbPersistence(configuration)
//                .AddMongoDbEventStorePersistence(configuration)
                .AddInProcessMessageBus();
        }
    }
}

================================================
FILE: src/WebApi/Extensions/ApplicationBuilderExtensions.cs
================================================
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;

namespace WebApi.Extensions
{
    public static class ApplicationBuilderExtensions
    {
        public static IApplicationBuilder UseAspNetInfrastructure(this IApplicationBuilder app,
            IWebHostEnvironment hostingEnvironment,
            IConfiguration configuration)
        {
            return app.UseResponseCompression()
                .UseApplicationHeaders(hostingEnvironment, configuration)
                .UseSwaggerConf()
                .UseMvc()
                .UseHsts();
        }
        
        public static IApplicationBuilder UseSwaggerConf(this IApplicationBuilder app)
        {
            return app
                .UseSwagger()
                .UseSwaggerUI(c =>
                {
                    c.RoutePrefix = "";
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "PimReadProjector API Swagger v1");
                });
        }

        public static IApplicationBuilder UseApplicationHeaders(this IApplicationBuilder app,
            IWebHostEnvironment hostingEnvironment,
            IConfiguration configuration)
        {
            var appVersion = configuration["APP_VERSION"] ??
                             Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>().Version;

            return app.Use(async (context, func) =>
            {
                context.Response.OnStarting(state =>
                {
                    var httpContext = (HttpContext) state;

                    httpContext.Response.Headers.Add("X-Environment", new[] {hostingEnvironment.EnvironmentName});
                    httpContext.Response.Headers.Add("X-Version", new[] {appVersion});
                    return Task.FromResult(0);
                }, context);

                await func.Invoke();
            });
        }
    }
}

================================================
FILE: src/WebApi/Extensions/OriginalNameCamelCaseContractResolver.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace WebApi.Extensions
{
    internal class OriginalNameCamelCaseContractResolver : CamelCasePropertyNamesContractResolver
    {
        private readonly string[] _namespaces;

        public OriginalNameCamelCaseContractResolver(Type[] types)
            : this(types.Select(t => t.Namespace).ToArray())
        {
        }

        public OriginalNameCamelCaseContractResolver(string[] namespaces)
        {
            _namespaces = namespaces;
        }

        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var list = base.CreateProperties(type, memberSerialization);
            if (!_namespaces.Any(x => type.Namespace.StartsWith(x)))
            {
                return list;
            }

            foreach (var prop in list)
            {
                prop.PropertyName = NamingStrategy.GetPropertyName(prop.UnderlyingName, false);
            }

            return list;
        }
    }
}

================================================
FILE: src/WebApi/Extensions/ServiceCollectionExtensions.cs
================================================
using System;
using System.IO;
using System.Reflection;
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;

namespace WebApi.Extensions
{
    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddAspNetInfrastructure(this IServiceCollection services)
        {
            services
                .AddSwaggerGen(c =>
                {
                    c.SwaggerDoc("v1", new Info
                    {
                        Title = "Pim translation API Swagger",
                        Version = "v1"
                    });

                    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.XML";
                    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                    if (File.Exists(xmlPath))
                    {
                        c.IncludeXmlComments(xmlPath);
                    }
                })
                .AddResponseCompression()
                .AddMvc()                
                .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
                .AddFluentValidation(fv => fv.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()));
            return services;
        }
    }
}

================================================
FILE: src/WebApi/Program.cs
================================================
using System.Threading.Tasks;
using Infrastructure;
using Infrastructure.Logging;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace WebApi
{
    public class Program
    {
        public static Task Main(string[] args) => CreateHostBuilder(args).Build().RunAsync();

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .UseStartup<Startup>()
                        .ConfigureLoggingPlumbing()
                        .CaptureStartupErrors(true);
                });
    }
}

================================================
FILE: src/WebApi/Properties/launchSettings.json
================================================
{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false, 
    "anonymousAuthentication": true, 
    "iisExpress": {
      "applicationUrl": "http://localhost:58681",
      "sslPort": 44388
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "WebApi": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

================================================
FILE: src/WebApi/Startup.cs
================================================
using Infrastructure; 
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using WebApi.Extensions;

namespace WebApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services
                .AddInfrastructure(Configuration)
                .AddAspNetInfrastructure();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseAspNetInfrastructure(env, Configuration);
        }
    }
}

================================================
FILE: src/WebApi/UseCases/AddContentToProduct/AddContentToProductCommandValidator.cs
================================================
using Application.UseCases.AddContentToProduct;
using FluentValidation;

namespace WebApi.UseCases.AddContentToProduct
{
    public class AddContentToProductCommandValidator: AbstractValidator<AddContentToProductCommand> {
        public AddContentToProductCommandValidator() {
            RuleFor(x => x.Description).NotNull();
            RuleFor(x => x.Title).NotNull();
            RuleFor(x => x.AttributeId).NotNull();
            RuleFor(x => x.ProductId).NotNull(); 
            RuleFor(x => x.AttributeValueId).NotNull(); 
        }
    }
}

================================================
FILE: src/WebApi/UseCases/AddContentToProduct/ProductController.cs
================================================
using System.Threading;
using System.Threading.Tasks;
using Application.UseCases.AddContentToProduct;
using Infrastructure.Messaging.Mediator;
using Microsoft.AspNetCore.Mvc;

namespace WebApi.UseCases.AddContentToProduct
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        private readonly IMediator _mediator;

        public ProductsController(IMediator mediator) => _mediator = mediator;

        // POST api/products
        [HttpPost]
        public async Task Post([FromBody] AddContentToProductCommand command, CancellationToken cancellationToken)
        {
            await _mediator.SendAsync(command, cancellationToken);
        }
    }
}

================================================
FILE: src/WebApi/UseCases/CreateProduct/CreateProductCommandValidator.cs
================================================
using Application.UseCases.CreateProduct;
using FluentValidation;

namespace WebApi.UseCases.CreateProduct
{
    public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
    {
        public CreateProductCommandValidator()
        {
            RuleFor(x => x.BrandId).NotNull();
            RuleFor(x => x.CategoryId).NotNull();
            RuleFor(x => x.ProductCode).NotNull();
        }
    }
}

================================================
FILE: src/WebApi/UseCases/CreateProduct/ProductsController.cs
================================================
using System.Threading;
using System.Threading.Tasks;
using Application.UseCases.CreateProduct;
using Infrastructure.Messaging.Mediator;
using Microsoft.AspNetCore.Mvc;

namespace WebApi.UseCases.CreateProduct
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        private readonly IMediator _mediator;

        public ProductsController(IMediator mediator) => _mediator = mediator;

        // POST api/products
        [HttpPost]
        public async Task Post([FromBody] CreateProductCommand command, CancellationToken cancellationToken)
        {
            await _mediator.SendAsync(command, cancellationToken);
        }
    }
}

================================================
FILE: src/WebApi/WebApi.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>netcoreapp3.0</TargetFramework>
        <LangVersion>latest</LangVersion>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="FluentValidation.AspNetCore" Version="8.5.0" />
    </ItemGroup>

    <ItemGroup>
      <Content Update="appsettings.Development.json">
        <DependentUpon>appsettings.json</DependentUpon>
      </Content>
      <Content Update="appsettings.Production.json">
        <DependentUpon>appsettings.json</DependentUpon>
      </Content>
      <Content Update="appsettings.Staging.json">
        <DependentUpon>appsettings.json</DependentUpon>
      </Content>
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\Application\Application.csproj" />
      <ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
    </ItemGroup>

</Project>


================================================
FILE: src/WebApi/app.config
================================================
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

================================================
FILE: src/WebApi/appsettings.Development.json
================================================
{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}


================================================
FILE: src/WebApi/appsettings.Production.json
================================================
{
  "Serilog": {
    "Console": false,
    "MinimumLevel": {
      "Default": "Error"
    }
  }
}

================================================
FILE: src/WebApi/appsettings.Staging.json
================================================
{
  "Serilog": {
    "Console": false,
    "MinimumLevel": {
      "Default": "Error"
    }
  }
}

================================================
FILE: src/WebApi/appsettings.json
================================================
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "Serilog": {
    "GraylogIp": "",
    "GraylogPort": 0,
    "Console": true,
    "FacilityName": "service-name",
    "MinimumLevel": {
      "Default": "Verbose",
      "Override": {
        "Microsoft": "Information",
        "System": "Warning",
        "System.Net.Http.HttpClient": "Warning"
      }
    }
  },
  "AllowedHosts": "*"
}


================================================
FILE: test/Application.Tests/Application.Tests.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netcoreapp3.0</TargetFramework>

        <IsPackable>false</IsPackable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="FluentValidation.AspNetCore" Version="8.5.0" />
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
        <PackageReference Include="xunit" Version="2.4.1" />
        <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\..\src\Application\Application.csproj" />
      <ProjectReference Include="..\..\src\Infrastructure\Infrastructure.csproj" />
      <ProjectReference Include="..\TestBase\TestBase.csproj" />
    </ItemGroup>

</Project>


================================================
FILE: test/Application.Tests/CommandHandlerTestBase.cs
================================================
using Microsoft.Extensions.DependencyInjection;
using TestBase;

namespace Application.Tests
{
    public abstract class CommandHandlerTestBase: TestBaseWithIoC
    {
        protected CommandHandlerTestBase()
        {
            ConfigureServices(services =>
            {
                services
                    .AddInProcessMessageBus();
            }).
            Build();
        } 
    }
}

================================================
FILE: test/Application.Tests/CreateProductCommandHandlerTests.cs
================================================
using System.Threading;
using System.Threading.Tasks;
using Application.UseCases.CreateProduct;
using Domain.ProductContext;
using Xunit;

namespace Application.Tests
{
    public class CreateProductCommandHandlerTests : CommandHandlerTestBase
    {
        private readonly CreateProductCommandHandler _sut;
        private readonly IProductRepository _repository;

        public CreateProductCommandHandlerTests()
        {
            _repository = FakeAndRegister<IProductRepository>();
            _sut = new CreateProductCommandHandler(_repository);
        }

        [Fact]
        public async Task Should_Persist_Created_Product()
        {
            // Given
            var expectedCategoryId = Random<int>();
            var expectedBrandId = Random<int>();
            var expectedProductCode = Random<string>();

            // When
            await _sut.Handle(new CreateProductCommand(expectedCategoryId, expectedBrandId, expectedProductCode),
                CancellationToken.None);

            // Then
            Verify(() => _repository.Save(Matches<Product>(c => c.Category.Id == expectedCategoryId
                                                                && c.Brand.Id == expectedBrandId
                                                                && c.Code == expectedProductCode),
                Ignore<CancellationToken>()));
        }
    }
}

================================================
FILE: test/Domain.Specs/Domain.Specs.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netcoreapp3.0</TargetFramework>

        <IsPackable>false</IsPackable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="FluentValidation.AspNetCore" Version="8.5.0" />
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
        <PackageReference Include="xunit" Version="2.4.1" />
        <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\TestBase\TestBase.csproj" />
    </ItemGroup>


</Project>


================================================
FILE: test/Domain.Specs/ProductSpecs/WhenContentAddedToProduct.cs
================================================
using System;
using System.Linq;
using Domain.ProductContext;
using Domain.ProductContext.Events;
using TestBase;
using Xunit;

namespace Domain.Specs.ProductSpecs
{
    public class WhenContentAddedToProduct : SpecBase<Product>
    {
        [Fact]
        public void Content_Should_Be_Added()
        {
            var productId = Random<Guid>();
            var title = Random<string>();
            var description = Random<string>();
            var attributeId = Random<int>();
            var attributeValueId = Random<int>();

            var @event = new ContentAddedToProduct(
                productId,
                title,
                description, new AttributeRef(attributeId, attributeValueId));

            ScenarioForExisting()
                .Given(
                    () => Product.Create(productId, Random<int>(), Random<int>(), Random<string>()))
                .When(
                    product => product.AddContent(title, description, new AttributeRef(attributeId, attributeValueId))
                )
                .Then(@event)
                .AlsoAssert(
                    product => product.Contents
                        .Any(x => x.Title == title)
                );
        }

        [Fact]
        public void Given_Product_Has_DifferentType_Attribute_Then_Business_Exception_Should_Be_Thrown()
        {
            ScenarioForExisting()
                .Given(
                    () =>
                    {
                        var aggregate = Product.Create(Random<Guid>(), Random<int>(), Random<int>(), Random<string>());
                        aggregate.AddContent(Random<string>(), Random<string>(),
                            new AttributeRef(Random<int>(), Random<int>()));
                        return aggregate;
                    })
                .When(
                    product => product.AddContent(Random<string>(), Random<string>(),
                        new AttributeRef(Random<int>(), Random<int>()))
                )
                .ThenThrows<BusinessException>("Given attribute type should belong to any content of product as slicer");
        }

        [Fact]
        public void Given_Product_Has_Same_Attribute_Then_Business_Exception_Should_Be_Thrown()
        {
            var anAttribute = new AttributeRef(Random<int>(), Random<int>());
            ScenarioForExisting()
                .Given(
                    () =>
                    {
                        var aggregate = Product.Create(Random<Guid>(), Random<int>(), Random<int>(), Random<string>());
                        aggregate.AddContent(Random<string>(), Random<string>(), anAttribute);
                        return aggregate;
                    })
                .When(
                    product => product.AddContent(Random<string>(), Random<string>(), anAttribute)
                )
                .ThenThrows<BusinessException>("Same content already exists with given attribute");
        }
    }
}

================================================
FILE: test/Domain.Specs/ProductSpecs/WhenCreated.cs
================================================
using System;
using Domain.ProductContext;
using Domain.ProductContext.Events;
using TestBase;
using Xunit;

namespace Domain.Specs.ProductSpecs
{
    public class WhenCreated : SpecBase<Product>
    {
        [Fact]
        public void Aggregate_Should_Be_Created_And_Publish_ProductCreated_Event()
        {
            var productId = Random<Guid>();
            var brandId = Random<int>();
            var productCode = Random<string>();
            var categoryId = Random<int>();

            var productCreated = new ProductCreated(
                productId,
                productCode,
                brandId, categoryId);

            ScenarioFor(
                    () => Product.Create(productId, categoryId, brandId, productCode)
                )
                .When(product => { })
                .Then(productCreated);
        }
    }
}

================================================
FILE: test/Domain.Specs/ProductSpecs/WhenProductApproved.cs
================================================
using System;
using Domain.ProductContext;
using Domain.ProductContext.Events;
using TestBase;
using Xunit;

namespace Domain.Specs.ProductSpecs
{
    public class WhenProductApproved : SpecBase<Product>
    {
        [Fact]
        public void Given_Product_Has_No_Content_Business_Exception_Should_Be_Thrown()
        {
            var productId = Random<Guid>();

            ScenarioForExisting()
                .Given(
                    () =>
                    {
                        var aggregate = Product.Create(productId, Random<int>(), Random<int>(), Random<string>());
                        return aggregate;
                    })
                .When(
                    product => product.Approve())
                .ThenThrows<BusinessException>("Product must have at least one content");
        }

        [Fact]
        public void Given_Product_Has_No_Variant_Business_Exception_Should_Be_Thrown()
        {
            var productId = Random<Guid>();

            ScenarioForExisting()
                .Given(
                    () =>
                    {
                        var aggregate = Product.Create(productId, Random<int>(), Random<int>(), Random<string>());
                        aggregate.AddContent(Random<string>(), Random<string>(),
                            new AttributeRef(Random<int>(), Random<int>()));
                        return aggregate;
                    })
                .When(
                    product => product.Approve())
                .ThenThrows<BusinessException>("Product must have at least one variant");
        }

        [Fact]
        public void Given_Product_Has_No_Image_Business_Exception_Should_Be_Thrown()
        {
            var productId = Random<Guid>();

            ScenarioForExisting()
                .Given(
                    () =>
                    {
                        var aggregate = Product.Create(productId, Random<int>(), Random<int>(), Random<string>());
                        var slicerAttribute = new AttributeRef(Random<int>(), Random<int>());
                        aggregate.AddContent(Random<string>(), Random<string>(), slicerAttribute);
                        aggregate.AddVariant(Random<string>(), slicerAttribute,
                            new AttributeRef(Random<int>(), Random<int>()));
                        return aggregate;
                    })
                .When(
                    product => product.Approve())
                .ThenThrows<BusinessException>("Product must have at least one image");
        }

        [Fact]
        public void Product_Should_Be_Approved()
        {
            var productId = Random<Guid>();
            var @event = new ProductApproved(productId);

            ScenarioForExisting()
                .Given(
                    () =>
                    {
                        var aggregate = Product.Create(productId, Random<int>(), Random<int>(), Random<string>());
                        var slicerAttribute = new AttributeRef(Random<int>(), Random<int>());
                        aggregate.AddContent(Random<string>(), Random<string>(), slicerAttribute);
                        var varianterAttribute = new AttributeRef(Random<int>(), Random<int>());
                        aggregate.AddVariant(Random<string>(), slicerAttribute, varianterAttribute);
                        aggregate.AssignImage(Random<ImageRef>(), varianterAttribute);
                        return aggregate;
                    })
                .When(
                    product => product.Approve())
                .Then(@event)
                .AlsoAssert(product => product.IsApproved);
        }
    }
}

================================================
FILE: test/Domain.Specs/ProductSpecs/WhenVariantAddedToProduct.cs
================================================
using System;
using System.Linq;
using Domain.ProductContext;
using Domain.ProductContext.Events;
using TestBase;
using Xunit;

namespace Domain.Specs.ProductSpecs
{
    public class WhenVariantAddedToProduct : SpecBase<Product>
    {
        [Fact]
        public void Variant_Should_Be_Added()
        {
            var slicerAttributeId = Random<int>();
            var slicerAttributeValueId = Random<int>();
            var productId = Random<Guid>();
            var barcode = Random<String>();
            var varianterAttributeId = Random<int>();
            var varianterAttributeValueId = Random<int>();
            
            var @event = new VariantAddedToProduct(productId, barcode, 
                new AttributeRef(slicerAttributeId, slicerAttributeValueId),
                new AttributeRef(varianterAttributeId,  varianterAttributeValueId) );

            ScenarioForExisting()
                .Given(
                    () =>
                    {
                        var aggregate = Product.Create(productId, Random<int>(), Random<int>(), Random<string>());
                        aggregate.AddContent(Random<string>(), Random<string>(), new AttributeRef(slicerAttributeId, slicerAttributeValueId));
                        return aggregate;
                    })
                .When
                (
                    product => product.AddVariant(barcode,
                        new AttributeRef(slicerAttributeId, slicerAttributeValueId), 
                        new AttributeRef(varianterAttributeId, varianterAttributeValueId))
                )
                .Then(@event)
                .AlsoAssert(
                    product => product.Contents.SelectMany(c => c.Variants)
                        .Any(x => x.Barcode == barcode)
                );
        }
        
        [Fact]
        public void Given_Product_Has_No_Content_With_Given_Slicer_Should_Throw_Business_Exception()
        {
            var slicerAttributeId = Random<int>();
            var slicerAttributeValueId = Random<int>();
             var barcode = Random<String>();
            var varianterAttributeId = Random<int>();
            var varianterAttributeValueId = Random<int>();
             
            ScenarioForExisting()
                .Given(
                    () => Product.Create(Random<Guid>(), Random<int>(), Random<int>(), Random<string>()))
                .When
                (
                    product => product.AddVariant(barcode,
                        new AttributeRef(slicerAttributeId, slicerAttributeValueId), 
                        new AttributeRef(varianterAttributeId, varianterAttributeValueId))
                )
                .ThenThrows<BusinessException>("No content found with given slicer attribute.");
        }
    }
}

================================================
FILE: test/Infrastructure.Tests/Infrastructure.Tests.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netcoreapp3.0</TargetFramework>

        <IsPackable>false</IsPackable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="FluentValidation.AspNetCore" Version="8.5.0" />
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
        <PackageReference Include="xunit" Version="2.4.1" />
        <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\..\src\Infrastructure\Infrastructure.csproj" />
      <ProjectReference Include="..\TestBase\TestBase.csproj" />
    </ItemGroup>

</Project>


================================================
FILE: test/Infrastructure.Tests/MediatorTests.cs
================================================
using System;
using System.Threading;
using System.Threading.Tasks;
using Application;
using Infrastructure.Messaging.Mediator;
using Microsoft.Extensions.DependencyInjection;
using TestBase;
using Xunit;

namespace Infrastructure.Tests
{
    public class MediatorTests : TestBaseWithIoC
    {
        internal static Guid Command_Should_Be_Handled_Test_Assertion = Guid.Empty;
        public MediatorTests()
        {
            ConfigureServices(services =>
                {
                    services
                        .AddInProcessMessageBus();
                }).
                Build();
        }

        [Fact]
        public async Task Command_Should_Be_Handled()
        {
           var bus = GetRequiredService<IMediator>();

           var newGuid = Guid.NewGuid();
           await bus.SendAsync(new TestCommand(newGuid));
           
           Assert.Equal(newGuid, Command_Should_Be_Handled_Test_Assertion);
        }
        
    }
    
    public class TestCommand
    {
        public TestCommand(Guid test)
        {
            Test = test;
        }

        public Guid Test { get; }    
    }

    public class TestCommandHandler : ICommandHandler<TestCommand>
    {
        public Task Handle(TestCommand message, CancellationToken cancellationToken)
        {
            MediatorTests.Command_Should_Be_Handled_Test_Assertion = message.Test;
            
            return Task.CompletedTask;
        }
    }
}

================================================
FILE: test/TestBase/InMemoryDatabaseFixture.cs
================================================
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq.Expressions;
using ServiceStack.OrmLite;
using ServiceStack.OrmLite.Legacy;
using ServiceStack.OrmLite.Sqlite;

namespace TestBase
{
    public class InMemoryDatabaseFixture : IDisposable
    {
        private readonly OrmLiteConnectionFactory _dbFactory =
            new OrmLiteConnectionFactory(":memory:", SqliteOrmLiteDialectProvider.Instance);

        private IDbConnection _db;

        public IDbConnection OpenConnection() => _dbFactory.OpenDbConnection();

        public void Insert<T>(IEnumerable<T> items, string tableName)
        {
            _db = _db ?? OpenConnection();
            _db.DropAndCreateTable<T>(tableName);
            foreach (var item in items)
            {
                _db.Insert(tableName, item);
            }
        }

        public void Dispose()
        {
            _db?.Dispose();
            _db = null;
        }
    }

    public static class GenericTableExtensions
    {
        static object ExecWithAlias<T>(string table, Func<object> fn)
        {
            var modelDef = typeof(T).GetModelMetadata();
            lock (modelDef)
            {
                var hold = modelDef.Alias;
                try
                {
                    modelDef.Alias = table;
                    return fn();
                }
                finally
                {
                    modelDef.Alias = hold;
                }
            }
        }

        public static void DropAndCreateTable<T>(this IDbConnection db, string table)
        {
            ExecWithAlias<T>(table, () =>
            {
                db.DropAndCreateTable<T>();
                return null;
            });
        }

        public static long Insert<T>(this IDbConnection db, string table, T obj, bool selectIdentity = false)
        {
            return (long) ExecWithAlias<T>(table, () => db.Insert(obj, selectIdentity));
        }

        public static List<T> Select<T>(this IDbConnection db, string table,
            Func<SqlExpression<T>, SqlExpression<T>> expression)
        {
            return (List<T>) ExecWithAlias<T>(table, () => db.Select<T>(expression));
        }

        public static int Update<T>(this IDbConnection db, string table, T item, Expression<Func<T, bool>> where)
        {
            return (int) ExecWithAlias<T>(table, () => db.Update(item, where));
        }
    }
}

================================================
FILE: test/TestBase/ScenarioFor.cs
================================================
using FluentAssertions;
using KellermanSoftware.CompareNetObjects;
using System;
using System.Linq;
using Domain;

namespace TestBase
{
    public class ScenarioFor<TAggregateRoot> where TAggregateRoot : IAggregateRoot
    {
        private TAggregateRoot _aggregateRoot;
        private Action<TAggregateRoot>[] _whens;

        public ScenarioFor(Func<TAggregateRoot> constructor) => _aggregateRoot = constructor();

        public ScenarioFor<TAggregateRoot> When(params Action<TAggregateRoot>[] whens)
        {
            _whens = whens;
            return this;
        }

        public ScenarioFor<TAggregateRoot> With(Action<TAggregateRoot> act)
        {
            act(_aggregateRoot);
            return this;
        }

        public void Then(params object[] events)
        {
            var logic = new CompareLogic().Compare(_aggregateRoot.GetUncommittedChanges().ToArray(), events);
            logic.AreEqual.Should().Be(true, "Expected domain event(s) should be fired");
        }

        public void ThenThrows<TException>(string message = "") where TException : Exception
        {
            Action act = () =>
            {
                foreach (var action in _whens)
                {
                    action(_aggregateRoot);
                }
            };

            act.Should().Throw<TException>(message);
        }
    }
}

================================================
FILE: test/TestBase/ScenarioForExisting.cs
================================================
using System;
using System.Linq;
using Domain;
using FluentAssertions;
using KellermanSoftware.CompareNetObjects;

namespace TestBase
{
    public class ScenarioForExisting<TAggregateRoot> where TAggregateRoot : IAggregateRoot
    {
        private TAggregateRoot _aggregateRoot;
        private Action<TAggregateRoot>[] _whens;

        public ScenarioForExisting<TAggregateRoot> Given(Func<TAggregateRoot> aggregateRoot)
        {
            _aggregateRoot = aggregateRoot();
            _aggregateRoot.MarkChangesAsCommitted();
            return this;
        }

        public ScenarioForExisting<TAggregateRoot> When(params Action<TAggregateRoot>[] whens)
        {
            _whens = whens;
            return this;
        }

        public ScenarioForExisting<TAggregateRoot> With(Action<TAggregateRoot> act)
        {
            act(_aggregateRoot);
            return this;
        }

        public ScenarioForExisting<TAggregateRoot> Then(params object[] events)
        {
            foreach (var action in _whens)
            {
                action(_aggregateRoot);
            }

            var logic = new CompareLogic().Compare(_aggregateRoot.GetUncommittedChanges().ToArray(), events);
            logic.AreEqual.Should().Be(true, "Expected domain event(s) should be fired");
            return this;
        }

        public ScenarioForExisting<TAggregateRoot> AlsoAssert(Func<TAggregateRoot, bool> expression)
        {
            expression(_aggregateRoot).Should().Be(true, "AlsoAssert is not validated maybe event is not applied");
            return this;
        }

        public void ThenNone()
        {
            foreach (var action in _whens)
            {
                action(_aggregateRoot);
            }

            _aggregateRoot.GetUncommittedChanges().ToArray().Should()
                .BeEmpty("Aggregate should not have any events! But founded.");
        }

        public void ThenThrows<TException>(string message = null) where TException : Exception
        {
            Action act = () =>
            {
                foreach (var action in _whens)
                {
                    action(_aggregateRoot);
                }
            };

            if (message != null)
                act.Should().Throw<TException>().WithMessage(message);
            else
                act.Should().Throw<TException>();
        }
    }
}

================================================
FILE: test/TestBase/SpecBase.cs
================================================
using System;
using System.Linq;
using AutoFixture;
using Domain;

namespace TestBase
{
    public abstract class SpecBase<TAggregate> : SpecBase where TAggregate : IAggregateRoot
    {
        protected virtual ScenarioForExisting<TAggregate> ScenarioForExisting()
        {
            return base.ScenarioForExisting<TAggregate>();
        }
        
        protected ScenarioFor<TAggregate> ScenarioFor(Func<TAggregate> constructor)
        {
            return base.ScenarioFor<TAggregate>(constructor);
        }
    }

    public abstract class SpecBase
    {
        protected virtual ScenarioForExisting<TAggregate> ScenarioForExisting<TAggregate>() where TAggregate : IAggregateRoot
        {
            return new ScenarioForExisting<TAggregate>();
        }
        
        protected virtual ScenarioFor<TAggregate> ScenarioFor<TAggregate>(Func<TAggregate> constructor) where TAggregate : IAggregateRoot
        {
            return new ScenarioFor<TAggregate>(constructor);
        }
        
        private static readonly Fixture fixture = new Fixture();

        protected T Random<T>()
        {
            if (typeof(T) == typeof(long))
            {
                return (T) Convert.ChangeType(new Random().Next(),
                    typeof(T)); // workaround to avoid autoFixture's similer long values 
            }

            return fixture.Create<Generator<T>>().First();
        }
    }
}


================================================
FILE: test/TestBase/TestBase.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netcoreapp3.0</TargetFramework>

        <IsPackable>false</IsPackable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="AutoFixture" Version="4.11.0" />
        <PackageReference Include="CompareNETObjects" Version="4.63.0" />
        <PackageReference Include="FakeItEasy" Version="5.2.0" />
        <PackageReference Include="FluentAssertions" Version="5.9.0" />
        <PackageReference Include="FluentValidation.AspNetCore" Version="8.5.0" />
        <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
        <PackageReference Include="ServiceStack.OrmLite.Sqlite" Version="5.7.0" />
        <PackageReference Include="xunit" Version="2.4.1" />
        <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\..\src\Domain\Domain.csproj" />
    </ItemGroup>

</Project>


================================================
FILE: test/TestBase/TestBaseWithInMemoryDb.cs
================================================
using Xunit;

namespace TestBase
{
    public abstract class TestBaseWithInMemoryDb : TestBaseWithIoC, IClassFixture<InMemoryDatabaseFixture>
    {
        protected static InMemoryDatabaseFixture InMemoryDb { get; private set; }
        

        protected TestBaseWithInMemoryDb(InMemoryDatabaseFixture inMemoryDatabaseFixture)
        {
            InMemoryDb = inMemoryDatabaseFixture;
        }

        public override void Dispose()
        {
            base.Dispose();
            InMemoryDb.Dispose();
        }
    }
}

================================================
FILE: test/TestBase/TestBaseWithIoC.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using AutoFixture;
using FakeItEasy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace TestBase
{
    public abstract class TestBaseWithIoC : IDisposable
    {
        private IServiceProvider _localResolver;
        private readonly IServiceCollection _services;

        protected TestBaseWithIoC() => _services = new ServiceCollection();

        protected IConfiguration Configuration => InMemoryConfiguration != null
            ? new ConfigurationBuilder().AddInMemoryCollection(InMemoryConfiguration).Build()
            : new ConfigurationBuilder().AddJsonFile(ConfigJsonFilename).Build();

        protected virtual IDictionary<string, string> InMemoryConfiguration => null;

        protected virtual string ConfigJsonFilename => "appsettings.test.json";

        public virtual void Dispose()
        {
        }

        protected TestBaseWithIoC ConfigureServices(Action<IServiceCollection> builder)
        {
            builder(_services);
            return this;
        }

        public void Build()
        {
            _localResolver = _services.BuildServiceProvider();
        }

        protected T GetRequiredService<T>() => _localResolver.GetRequiredService<T>();

        protected virtual T Fake<T>(bool strict = false) where T : class
        {
            return A.Fake<T>(cfg =>
            {
                if (strict)
                {
                    cfg.Strict();
                }
            });
        }

        protected virtual T FakeAndRegister<T>(bool strict = false)
            where T : class
        {
            T a = Fake<T>();

            _services.AddSingleton(c => a);

            return a;
        }

        protected virtual T FakeAndRegisterWithType<T>(bool strict = false)
            where T : class
        {
            T a = Fake<T>();

            _services.AddSingleton(typeof(T), c => a);

            return a;
        }

        protected virtual void Verify(Expression<Action> callSpecification, int numberOfTimes = 1)
        {
            A.CallTo(callSpecification).MustHaveHappened(numberOfTimes, Times.Exactly);
        }

        protected virtual void Stub<T>(Expression<Func<T>> callSpecification, T stubbingValue)
        {
            A.CallTo(callSpecification).Returns(stubbingValue);
        }

        protected virtual T Ignore<T>() => A<T>.Ignored;

        protected T Matches<T>(Expression<Func<T, bool>> predicate) => A<T>.That.Matches(predicate);

        private static readonly Fixture fixture = new Fixture();

        protected T Random<T>()
        {
            if (typeof(T) == typeof(long))
            {
                return (T) Convert.ChangeType(new Random().Next(),
                    typeof(T)); // workaround to avoid autoFixture's similer long values 
            }

            return fixture.Create<Generator<T>>().First();
        }
    }
}
Download .txt
gitextract_22c5k2bq/

├── .gitignore
├── Directory.build.props
├── LICENSE
├── README.md
├── aspnet-core-clean-arch.sln
├── src/
│   ├── Application/
│   │   ├── Application.csproj
│   │   ├── ICommandHandler.cs
│   │   ├── IEventHandler.cs
│   │   ├── IQueryHandler.cs
│   │   └── UseCases/
│   │       ├── AddContentToProduct/
│   │       │   ├── AddContentToProductCommand.cs
│   │       │   └── AddContentToProductCommandHandler.cs
│   │       └── CreateProduct/
│   │           ├── CreateProductCommand.cs
│   │           └── CreateProductCommandHandler.cs
│   ├── Domain/
│   │   ├── Abstraction/
│   │   │   ├── AggregateRoot.cs
│   │   │   ├── BaseDomainEvent.cs
│   │   │   ├── Entity.cs
│   │   │   ├── IRepository.cs
│   │   │   ├── InstanceEventRouter.cs
│   │   │   └── ValueObject.cs
│   │   ├── BusinessException.cs
│   │   ├── Domain.csproj
│   │   └── ProductContext/
│   │       ├── AttributeRef.cs
│   │       ├── BrandRef.cs
│   │       ├── CategoryRef.cs
│   │       ├── Content.cs
│   │       ├── Events/
│   │       │   ├── AttributeAddedToProduct.cs
│   │       │   ├── BaseProductEvent.cs
│   │       │   ├── ContentAddedToProduct.cs
│   │       │   ├── ImageAddedToProduct.cs
│   │       │   ├── ProductApproved.cs
│   │       │   ├── ProductCreated.cs
│   │       │   └── VariantAddedToProduct.cs
│   │       ├── IProductRepository.cs
│   │       ├── ImageRef.cs
│   │       ├── Product.cs
│   │       └── Variant.cs
│   ├── Infrastructure/
│   │   ├── Infrastructure.csproj
│   │   ├── Logging/
│   │   │   └── WebHostExtensions.cs
│   │   ├── Messaging/
│   │   │   └── Mediator/
│   │   │       ├── IMediator.cs
│   │   │       ├── Mediator.cs
│   │   │       └── ServiceCollectionExtensions.cs
│   │   ├── Persistence/
│   │   │   ├── AggregateNotFoundException.cs
│   │   │   ├── ConcurrencyException.cs
│   │   │   ├── EventStoreProductRepository.cs
│   │   │   ├── IEventStore.cs
│   │   │   ├── InMemory/
│   │   │   │   ├── EventStore/
│   │   │   │   │   └── InMemoryEventStore.cs
│   │   │   │   └── ServiceCollectionExtensions.cs
│   │   │   └── Mongo/
│   │   │       ├── EventStore/
│   │   │       │   └── MongoDbEventStore.cs
│   │   │       ├── IMongoDbContext.cs
│   │   │       ├── MongoDbContext.cs
│   │   │       ├── MongoDbProductRepository.cs
│   │   │       ├── MongoDbProvider.cs
│   │   │       ├── MongoDbRepository.cs
│   │   │       ├── MongoSettings.cs
│   │   │       └── ServiceCollectionExtensions.cs
│   │   └── ServiceCollectionExtensions.cs
│   └── WebApi/
│       ├── Extensions/
│       │   ├── ApplicationBuilderExtensions.cs
│       │   ├── OriginalNameCamelCaseContractResolver.cs
│       │   └── ServiceCollectionExtensions.cs
│       ├── Program.cs
│       ├── Properties/
│       │   └── launchSettings.json
│       ├── Startup.cs
│       ├── UseCases/
│       │   ├── AddContentToProduct/
│       │   │   ├── AddContentToProductCommandValidator.cs
│       │   │   └── ProductController.cs
│       │   └── CreateProduct/
│       │       ├── CreateProductCommandValidator.cs
│       │       └── ProductsController.cs
│       ├── WebApi.csproj
│       ├── app.config
│       ├── appsettings.Development.json
│       ├── appsettings.Production.json
│       ├── appsettings.Staging.json
│       └── appsettings.json
└── test/
    ├── Application.Tests/
    │   ├── Application.Tests.csproj
    │   ├── CommandHandlerTestBase.cs
    │   └── CreateProductCommandHandlerTests.cs
    ├── Domain.Specs/
    │   ├── Domain.Specs.csproj
    │   └── ProductSpecs/
    │       ├── WhenContentAddedToProduct.cs
    │       ├── WhenCreated.cs
    │       ├── WhenProductApproved.cs
    │       └── WhenVariantAddedToProduct.cs
    ├── Infrastructure.Tests/
    │   ├── Infrastructure.Tests.csproj
    │   └── MediatorTests.cs
    └── TestBase/
        ├── InMemoryDatabaseFixture.cs
        ├── ScenarioFor.cs
        ├── ScenarioForExisting.cs
        ├── SpecBase.cs
        ├── TestBase.csproj
        ├── TestBaseWithInMemoryDb.cs
        └── TestBaseWithIoC.cs
Download .txt
SYMBOL INDEX (271 symbols across 70 files)

FILE: src/Application/ICommandHandler.cs
  type ICommandHandler (line 6) | public interface ICommandHandler<in TMessage>
    method Handle (line 8) | Task Handle(TMessage message, CancellationToken cancellationToken);

FILE: src/Application/IEventHandler.cs
  type IEventHandler (line 6) | public interface IEventHandler <in TEvent>
    method Handle (line 8) | Task Handle(TEvent @event, CancellationToken cancellationToken);

FILE: src/Application/IQueryHandler.cs
  type IQueryHandler (line 6) | public interface IQueryHandler<in TMessage, TResult>
    method Handle (line 8) | Task<TResult> Handle(TMessage message, CancellationToken cancellationT...

FILE: src/Application/UseCases/AddContentToProduct/AddContentToProductCommand.cs
  class AddContentToProductCommand (line 5) | public class AddContentToProductCommand
    method AddContentToProductCommand (line 7) | public AddContentToProductCommand(Guid productId, string title, string...

FILE: src/Application/UseCases/AddContentToProduct/AddContentToProductCommandHandler.cs
  class AddContentToProductCommandHandler (line 7) | public class AddContentToProductCommandHandler : ICommandHandler<AddCont...
    method AddContentToProductCommandHandler (line 11) | public AddContentToProductCommandHandler(IProductRepository productRep...
    method Handle (line 16) | public async Task Handle(AddContentToProductCommand command, Cancellat...

FILE: src/Application/UseCases/CreateProduct/CreateProductCommand.cs
  class CreateProductCommand (line 3) | public class CreateProductCommand
    method CreateProductCommand (line 5) | public CreateProductCommand(int categoryId, int brandId, string produc...

FILE: src/Application/UseCases/CreateProduct/CreateProductCommandHandler.cs
  class CreateProductCommandHandler (line 8) | public class CreateProductCommandHandler : ICommandHandler<CreateProduct...
    method CreateProductCommandHandler (line 12) | public CreateProductCommandHandler(IProductRepository productRepository)
    method Handle (line 17) | public async Task Handle(CreateProductCommand command, CancellationTok...

FILE: src/Domain/Abstraction/AggregateRoot.cs
  type IAggregateRoot (line 8) | public interface IAggregateRoot
    method HasChanges (line 11) | bool HasChanges();
    method GetUncommittedChanges (line 12) | IEnumerable<object> GetUncommittedChanges();
    method MarkChangesAsCommitted (line 13) | void MarkChangesAsCommitted();
    method LoadsFromHistory (line 14) | void LoadsFromHistory(IEnumerable<object> history);
  class AggregateRoot (line 17) | public abstract class AggregateRoot<TKey> : IAggregateRoot
    method AggregateRoot (line 22) | public AggregateRoot()
    method HasChanges (line 31) | public bool HasChanges() => _changes.Any();
    method GetUncommittedChanges (line 33) | public IEnumerable<object> GetUncommittedChanges()
    method MarkChangesAsCommitted (line 38) | public void MarkChangesAsCommitted()
    method LoadsFromHistory (line 43) | public void LoadsFromHistory(IEnumerable<object> history)
    method Register (line 48) | protected void Register<TEvent>(Action<TEvent> handler)
    method ApplyChange (line 54) | protected void ApplyChange(object @event)
    method ApplyChange (line 59) | protected void ApplyChange(object @event, bool isNew)
    method IsTransient (line 78) | public virtual bool IsTransient()
    method Equals (line 99) | public override bool Equals(object obj)
    method GetHashCode (line 131) | public override int GetHashCode() => Id.GetHashCode();
    method Should (line 150) | protected void Should(Func<bool> predicate)
    method Should (line 155) | protected void Should(Func<bool> predicate, string otherwiseMessage)
    method Should (line 160) | protected void Should(bool clause)
    method Should (line 166) | protected void Should(bool clause, string otherwiseMessage)

FILE: src/Domain/Abstraction/BaseDomainEvent.cs
  class BaseDomainEvent (line 3) | public class BaseDomainEvent

FILE: src/Domain/Abstraction/Entity.cs
  class Entity (line 5) | public abstract class Entity
    method Entity (line 9) | protected Entity() => _router = new InstanceEventRouter();
    method Register (line 11) | protected void Register<TEvent>(Action<TEvent> handler)
    method Route (line 21) | public virtual void Route(object @event)

FILE: src/Domain/Abstraction/IRepository.cs
  type IRepository (line 11) | public interface IRepository<T, TKey> where T : AggregateRoot<TKey>
    method Save (line 13) | Task Save(T aggregate, CancellationToken cancellationToken);
    method Load (line 14) | Task<T> Load(TKey id, CancellationToken cancellationToken);

FILE: src/Domain/Abstraction/InstanceEventRouter.cs
  class InstanceEventRouter (line 9) | internal class InstanceEventRouter
    method InstanceEventRouter (line 16) | public InstanceEventRouter() => _handlers = new Dictionary<Type, Actio...
    method ConfigureRoute (line 27) | public void ConfigureRoute(Type @event, Action<object> handler)
    method ConfigureRoute (line 48) | public void ConfigureRoute<TEvent>(Action<TEvent> handler)
    method Route (line 63) | public void Route(object @event)

FILE: src/Domain/Abstraction/ValueObject.cs
  class ValueObject (line 11) | public abstract class ValueObject<TValueObject> : IEquatable<TValueObject>
    method Equals (line 14) | public bool Equals(TValueObject other)
    method Equals (line 31) | public override bool Equals(object obj)
    method GetHashCode (line 42) | public override int GetHashCode()

FILE: src/Domain/BusinessException.cs
  class BusinessException (line 5) | public class BusinessException : Exception
    method BusinessException (line 7) | public BusinessException()
    method BusinessException (line 11) | public BusinessException(string message) : base(message)
    method BusinessException (line 15) | public BusinessException(string message, Exception innerException) : b...

FILE: src/Domain/ProductContext/AttributeRef.cs
  class AttributeRef (line 3) | public class AttributeRef : ValueObject<AttributeRef>
    method AttributeRef (line 5) | public AttributeRef(int attributeId, int attributeValueId)

FILE: src/Domain/ProductContext/BrandRef.cs
  class BrandRef (line 3) | public class BrandRef : ValueObject<BrandRef>
    method BrandRef (line 5) | public BrandRef(int id, string name)

FILE: src/Domain/ProductContext/CategoryRef.cs
  class CategoryRef (line 3) | public class CategoryRef : ValueObject<CategoryRef>
    method CategoryRef (line 5) | public CategoryRef(int id, string name)

FILE: src/Domain/ProductContext/Content.cs
  class Content (line 10) | public class Content : Entity
    method Content (line 14) | internal Content(string title, string description, AttributeRef slicer...
    method Apply (line 23) | private void Apply(VariantAddedToProduct @event)
    method HasSameTypeSlicerAttribute (line 36) | public bool HasSameTypeSlicerAttribute(AttributeRef attribute)

FILE: src/Domain/ProductContext/Events/AttributeAddedToProduct.cs
  class AttributeAddedToProduct (line 5) | public class AttributeAddedToProduct : BaseProductEvent
    method AttributeAddedToProduct (line 7) | public AttributeAddedToProduct(Guid productId, AttributeRef attribute)

FILE: src/Domain/ProductContext/Events/BaseProductEvent.cs
  class BaseProductEvent (line 6) | public abstract class BaseProductEvent : BaseDomainEvent

FILE: src/Domain/ProductContext/Events/ContentAddedToProduct.cs
  class ContentAddedToProduct (line 5) | public class ContentAddedToProduct : BaseProductEvent
    method ContentAddedToProduct (line 7) | public ContentAddedToProduct(Guid productId, string title, string desc...

FILE: src/Domain/ProductContext/Events/ImageAddedToProduct.cs
  class ImageAddedToProduct (line 5) | public class ImageAddedToProduct :BaseProductEvent
    method ImageAddedToProduct (line 7) | public ImageAddedToProduct(Guid productId, AttributeRef varianterAttr,...

FILE: src/Domain/ProductContext/Events/ProductApproved.cs
  class ProductApproved (line 5) | public class ProductApproved : BaseProductEvent
    method ProductApproved (line 7) | public ProductApproved(Guid productId)

FILE: src/Domain/ProductContext/Events/ProductCreated.cs
  class ProductCreated (line 5) | public class ProductCreated : BaseProductEvent
    method ProductCreated (line 7) | public ProductCreated(Guid productId, string productCode, int brandId,...

FILE: src/Domain/ProductContext/Events/VariantAddedToProduct.cs
  class VariantAddedToProduct (line 5) | public class VariantAddedToProduct: BaseProductEvent
    method VariantAddedToProduct (line 7) | public VariantAddedToProduct(Guid productId, string barcode, Attribute...

FILE: src/Domain/ProductContext/IProductRepository.cs
  type IProductRepository (line 6) | public interface IProductRepository : IRepository<Product, Guid>

FILE: src/Domain/ProductContext/ImageRef.cs
  class ImageRef (line 3) | public class ImageRef : ValueObject<ImageRef>
    method ImageRef (line 5) | public ImageRef(string relativeUrl, string relativeThumbUrl)

FILE: src/Domain/ProductContext/Product.cs
  class Product (line 8) | public class Product : AggregateRoot<Guid>
    method Product (line 14) | public Product()
    method Create (line 24) | public static Product Create(Guid productId,
    method Apply (line 54) | private void Apply(ProductApproved @event) => IsApproved = true;
    method Apply (line 56) | private void Apply(ContentAddedToProduct @event) => _contents.Add(new ...
    method Apply (line 58) | private void Apply(AttributeAddedToProduct @event) => _attributes.Add(...
    method Apply (line 60) | private void Apply(VariantAddedToProduct @event) => _contents.First(c ...
    method Apply (line 62) | private void Apply(ProductCreated @event)
    method Apply (line 70) | private void Apply(ImageAddedToProduct @event)
    method AddContent (line 79) | public void AddContent(string title, string description, AttributeRef ...
    method AddVariant (line 93) | public void AddVariant(string barcode, AttributeRef slicerAttribute, A...
    method AddAttributeToContent (line 113) | public void AddAttributeToContent(AttributeRef attribute)
    method Approve (line 121) | public void Approve()
    method AssignImage (line 130) | public void AssignImage(ImageRef image, AttributeRef varianterAttr)

FILE: src/Domain/ProductContext/Variant.cs
  class Variant (line 7) | public class Variant : Entity
    method Variant (line 11) | internal Variant(string barcode, AttributeRef varianterAttribute)
    method Apply (line 19) | private void Apply(ImageAddedToProduct @event)
    method HasSameTypeVarianterAttribute (line 30) | public bool HasSameTypeVarianterAttribute(AttributeRef attribute)

FILE: src/Infrastructure/Logging/WebHostExtensions.cs
  class WebHostExtensions (line 13) | public static class WebHostExtensions
    method ConfigureLoggingPlumbing (line 15) | public static IWebHostBuilder ConfigureLoggingPlumbing(this IWebHostBu...

FILE: src/Infrastructure/Messaging/Mediator/IMediator.cs
  type IMediator (line 6) | public interface IMediator
    method SendAsync (line 8) | Task SendAsync<TMessage>(TMessage message, CancellationToken cancellat...
    method PublishAsync (line 9) | Task PublishAsync<TEvent>(TEvent message, CancellationToken cancellati...
    method SendAsync (line 10) | Task<TResult> SendAsync<TMessage, TResult>(TMessage @event, Cancellati...

FILE: src/Infrastructure/Messaging/Mediator/Mediator.cs
  class Mediator (line 9) | public class Mediator : IMediator
    method Mediator (line 13) | public Mediator(IServiceProvider serviceProvider)
    method SendAsync (line 18) | public Task SendAsync<TMessage>(TMessage message, CancellationToken ca...
    method PublishAsync (line 26) | public Task PublishAsync<TEvent>(TEvent @event, CancellationToken canc...
    method SendAsync (line 33) | public Task<TResult> SendAsync<TMessage, TResult>(TMessage message, Ca...

FILE: src/Infrastructure/Messaging/Mediator/ServiceCollectionExtensions.cs
  class ServiceCollectionExtensions (line 11) | public static class ServiceCollectionExtensions
    method AddInProcessMessageBus (line 13) | public static IServiceCollection AddInProcessMessageBus(this IServiceC...
    method ConnectImplementationsToTypesClosing (line 56) | private static void ConnectImplementationsToTypesClosing(Type openRequ...
    method IsMatchingWithInterface (line 110) | private static bool IsMatchingWithInterface(Type handlerType, Type han...
    method AddConcretionsThatCouldBeClosed (line 132) | private static void AddConcretionsThatCouldBeClosed(Type @interface, L...
    method CouldCloseTo (line 149) | private static bool CouldCloseTo(this Type openConcretion, Type closed...
    method CanBeCastTo (line 158) | private static bool CanBeCastTo(this Type pluggedType, Type pluginType)
    method FindInterfacesThatClose (line 167) | private static IEnumerable<Type> FindInterfacesThatClose(this Type plu...
    method FindInterfacesThatClosesCore (line 173) | private static IEnumerable<Type> FindInterfacesThatClosesCore(Type plu...
    method IsConcrete (line 205) | private static bool IsConcrete(this Type type)
    method IsOpenGeneric (line 210) | private static bool IsOpenGeneric(this Type type)
    method Fill (line 215) | private static void Fill<T>(this ICollection<T> list, T value)

FILE: src/Infrastructure/Persistence/AggregateNotFoundException.cs
  class AggregateNotFoundException (line 5) | public class AggregateNotFoundException : Exception

FILE: src/Infrastructure/Persistence/ConcurrencyException.cs
  class ConcurrencyException (line 5) | public class ConcurrencyException : Exception
    method ConcurrencyException (line 7) | public ConcurrencyException()
    method ConcurrencyException (line 11) | public ConcurrencyException(string message) : base(message)

FILE: src/Infrastructure/Persistence/EventStoreProductRepository.cs
  class EventStoreProductRepository (line 8) | internal class EventStoreProductRepository : IProductRepository
    method EventStoreProductRepository (line 12) | public EventStoreProductRepository(IEventStore eventStore) => _eventSt...
    method Save (line 14) | public async Task Save(Product aggregate, CancellationToken cancellati...
    method Load (line 20) | public async Task<Product> Load(Guid id, CancellationToken cancellatio...

FILE: src/Infrastructure/Persistence/IEventStore.cs
  type IEventStore (line 7) | public interface IEventStore
    method SaveEvents (line 9) | Task SaveEvents(Guid aggregateId, IEnumerable<object> events, int expe...
    method GetEventsForAggregate (line 10) | Task<List<object>> GetEventsForAggregate(Guid aggregateId);

FILE: src/Infrastructure/Persistence/InMemory/EventStore/InMemoryEventStore.cs
  class InMemoryEventStore (line 9) | public class InMemoryEventStore : IEventStore
    type EventDescriptor (line 13) | private struct EventDescriptor
      method EventDescriptor (line 19) | public EventDescriptor(Guid id, object eventData, int version)
    method InMemoryEventStore (line 27) | public InMemoryEventStore(IMediator publisher)
    method SaveEvents (line 35) | public async Task SaveEvents(Guid aggregateId, IEnumerable<object> eve...
    method GetEventsForAggregate (line 71) | public Task<List<object>> GetEventsForAggregate(Guid aggregateId)

FILE: src/Infrastructure/Persistence/InMemory/ServiceCollectionExtensions.cs
  class ServiceCollectionExtensions (line 7) | public static class ServiceCollectionExtensions
    method AddInMemoryEventStorePersistence (line 9) | public static IServiceCollection AddInMemoryEventStorePersistence(this...

FILE: src/Infrastructure/Persistence/Mongo/EventStore/MongoDbEventStore.cs
  class MongoDbEventStore (line 10) | public class MongoDbEventStore : IEventStore
    method MongoDbEventStore (line 16) | public MongoDbEventStore(IMediator publisher, IMongoDbContext _mongoDb...
    class EventDescriptor (line 22) | private class EventDescriptor
      method EventDescriptor (line 24) | public EventDescriptor(Guid id, object eventData, int version)
    method SaveEvents (line 40) | public async Task SaveEvents(Guid aggregateId, IEnumerable<object> eve...
    method GetEventsForAggregate (line 69) | public Task<List<object>> GetEventsForAggregate(Guid aggregateId)

FILE: src/Infrastructure/Persistence/Mongo/IMongoDbContext.cs
  type IMongoDbContext (line 5) | public interface IMongoDbContext
    method GetCollection (line 7) | IMongoCollection<T> GetCollection<T>(string collectionName);

FILE: src/Infrastructure/Persistence/Mongo/MongoDbContext.cs
  class MongoDbContext (line 6) | public sealed class MongoDbContext : IMongoDbContext
    method MongoDbContext (line 10) | public MongoDbContext(IMongoDatabase database)
    method GetCollection (line 15) | public IMongoCollection<T> GetCollection<T>(string collectionName)

FILE: src/Infrastructure/Persistence/Mongo/MongoDbProductRepository.cs
  class MongoDbProductRepository (line 7) | public class MongoDbProductRepository : MongoDbRepository<Product, Guid>...
    method MongoDbProductRepository (line 9) | public MongoDbProductRepository(IMongoDbContext context, IMediator med...

FILE: src/Infrastructure/Persistence/Mongo/MongoDbProvider.cs
  class MongoDbProvider (line 5) | public class MongoDbProvider
    method Provide (line 7) | public static IMongoDatabase Provide(MongoSettings settings)

FILE: src/Infrastructure/Persistence/Mongo/MongoDbRepository.cs
  class MongoDbRepository (line 13) | public abstract class MongoDbRepository<TAggregate, TKey> : IRepository<...
    method MongoDbRepository (line 21) | protected MongoDbRepository(IMediator mediator,IMongoDbContext context )
    method Save (line 27) | public virtual async Task Save(TAggregate aggregate, CancellationToken...
    method DispatchDomainEventsAsync (line 54) | protected async Task DispatchDomainEventsAsync(TAggregate aggregateRoo...
    method Load (line 63) | public virtual Task<TAggregate> Load(TKey id, CancellationToken cancel...

FILE: src/Infrastructure/Persistence/Mongo/MongoSettings.cs
  class MongoSettings (line 5) | public class MongoSettings

FILE: src/Infrastructure/Persistence/Mongo/ServiceCollectionExtensions.cs
  class ServiceCollectionExtensions (line 9) | public static class ServiceCollectionExtensions
    method AddMongoDbPersistence (line 11) | public static IServiceCollection AddMongoDbPersistence(this IServiceCo...
    method AddMongoDbEventStorePersistence (line 21) | public static IServiceCollection AddMongoDbEventStorePersistence(this ...

FILE: src/Infrastructure/ServiceCollectionExtensions.cs
  class ServiceCollectionExtensions (line 8) | public static class ServiceCollectionExtensions
    method AddInfrastructure (line 10) | public static IServiceCollection AddInfrastructure(this IServiceCollec...

FILE: src/WebApi/Extensions/ApplicationBuilderExtensions.cs
  class ApplicationBuilderExtensions (line 10) | public static class ApplicationBuilderExtensions
    method UseAspNetInfrastructure (line 12) | public static IApplicationBuilder UseAspNetInfrastructure(this IApplic...
    method UseSwaggerConf (line 23) | public static IApplicationBuilder UseSwaggerConf(this IApplicationBuil...
    method UseApplicationHeaders (line 34) | public static IApplicationBuilder UseApplicationHeaders(this IApplicat...

FILE: src/WebApi/Extensions/OriginalNameCamelCaseContractResolver.cs
  class OriginalNameCamelCaseContractResolver (line 9) | internal class OriginalNameCamelCaseContractResolver : CamelCaseProperty...
    method OriginalNameCamelCaseContractResolver (line 13) | public OriginalNameCamelCaseContractResolver(Type[] types)
    method OriginalNameCamelCaseContractResolver (line 18) | public OriginalNameCamelCaseContractResolver(string[] namespaces)
    method CreateProperties (line 23) | protected override IList<JsonProperty> CreateProperties(Type type, Mem...

FILE: src/WebApi/Extensions/ServiceCollectionExtensions.cs
  class ServiceCollectionExtensions (line 12) | public static class ServiceCollectionExtensions
    method AddAspNetInfrastructure (line 14) | public static IServiceCollection AddAspNetInfrastructure(this IService...

FILE: src/WebApi/Program.cs
  class Program (line 10) | public class Program
    method Main (line 12) | public static Task Main(string[] args) => CreateHostBuilder(args).Buil...
    method CreateHostBuilder (line 14) | public static IHostBuilder CreateHostBuilder(string[] args) =>

FILE: src/WebApi/Startup.cs
  class Startup (line 10) | public class Startup
    method Startup (line 12) | public Startup(IConfiguration configuration)
    method ConfigureServices (line 19) | public void ConfigureServices(IServiceCollection services)
    method Configure (line 26) | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

FILE: src/WebApi/UseCases/AddContentToProduct/AddContentToProductCommandValidator.cs
  class AddContentToProductCommandValidator (line 6) | public class AddContentToProductCommandValidator: AbstractValidator<AddC...
    method AddContentToProductCommandValidator (line 7) | public AddContentToProductCommandValidator() {

FILE: src/WebApi/UseCases/AddContentToProduct/ProductController.cs
  class ProductsController (line 9) | [Route("api/[controller]")]
    method ProductsController (line 15) | public ProductsController(IMediator mediator) => _mediator = mediator;
    method Post (line 18) | [HttpPost]

FILE: src/WebApi/UseCases/CreateProduct/CreateProductCommandValidator.cs
  class CreateProductCommandValidator (line 6) | public class CreateProductCommandValidator : AbstractValidator<CreatePro...
    method CreateProductCommandValidator (line 8) | public CreateProductCommandValidator()

FILE: src/WebApi/UseCases/CreateProduct/ProductsController.cs
  class ProductsController (line 9) | [Route("api/[controller]")]
    method ProductsController (line 15) | public ProductsController(IMediator mediator) => _mediator = mediator;
    method Post (line 18) | [HttpPost]

FILE: test/Application.Tests/CommandHandlerTestBase.cs
  class CommandHandlerTestBase (line 6) | public abstract class CommandHandlerTestBase: TestBaseWithIoC
    method CommandHandlerTestBase (line 8) | protected CommandHandlerTestBase()

FILE: test/Application.Tests/CreateProductCommandHandlerTests.cs
  class CreateProductCommandHandlerTests (line 9) | public class CreateProductCommandHandlerTests : CommandHandlerTestBase
    method CreateProductCommandHandlerTests (line 14) | public CreateProductCommandHandlerTests()
    method Should_Persist_Created_Product (line 20) | [Fact]

FILE: test/Domain.Specs/ProductSpecs/WhenContentAddedToProduct.cs
  class WhenContentAddedToProduct (line 10) | public class WhenContentAddedToProduct : SpecBase<Product>
    method Content_Should_Be_Added (line 12) | [Fact]
    method Given_Product_Has_DifferentType_Attribute_Then_Business_Exception_Should_Be_Thrown (line 39) | [Fact]
    method Given_Product_Has_Same_Attribute_Then_Business_Exception_Should_Be_Thrown (line 58) | [Fact]

FILE: test/Domain.Specs/ProductSpecs/WhenCreated.cs
  class WhenCreated (line 9) | public class WhenCreated : SpecBase<Product>
    method Aggregate_Should_Be_Created_And_Publish_ProductCreated_Event (line 11) | [Fact]

FILE: test/Domain.Specs/ProductSpecs/WhenProductApproved.cs
  class WhenProductApproved (line 9) | public class WhenProductApproved : SpecBase<Product>
    method Given_Product_Has_No_Content_Business_Exception_Should_Be_Thrown (line 11) | [Fact]
    method Given_Product_Has_No_Variant_Business_Exception_Should_Be_Thrown (line 28) | [Fact]
    method Given_Product_Has_No_Image_Business_Exception_Should_Be_Thrown (line 47) | [Fact]
    method Product_Should_Be_Approved (line 68) | [Fact]

FILE: test/Domain.Specs/ProductSpecs/WhenVariantAddedToProduct.cs
  class WhenVariantAddedToProduct (line 10) | public class WhenVariantAddedToProduct : SpecBase<Product>
    method Variant_Should_Be_Added (line 12) | [Fact]
    method Given_Product_Has_No_Content_With_Given_Slicer_Should_Throw_Business_Exception (line 47) | [Fact]

FILE: test/Infrastructure.Tests/MediatorTests.cs
  class MediatorTests (line 12) | public class MediatorTests : TestBaseWithIoC
    method MediatorTests (line 15) | public MediatorTests()
    method Command_Should_Be_Handled (line 25) | [Fact]
  class TestCommand (line 38) | public class TestCommand
    method TestCommand (line 40) | public TestCommand(Guid test)
  class TestCommandHandler (line 48) | public class TestCommandHandler : ICommandHandler<TestCommand>
    method Handle (line 50) | public Task Handle(TestCommand message, CancellationToken cancellation...

FILE: test/TestBase/InMemoryDatabaseFixture.cs
  class InMemoryDatabaseFixture (line 11) | public class InMemoryDatabaseFixture : IDisposable
    method OpenConnection (line 18) | public IDbConnection OpenConnection() => _dbFactory.OpenDbConnection();
    method Insert (line 20) | public void Insert<T>(IEnumerable<T> items, string tableName)
    method Dispose (line 30) | public void Dispose()
  class GenericTableExtensions (line 37) | public static class GenericTableExtensions
    method ExecWithAlias (line 39) | static object ExecWithAlias<T>(string table, Func<object> fn)
    method DropAndCreateTable (line 57) | public static void DropAndCreateTable<T>(this IDbConnection db, string...
    method Insert (line 66) | public static long Insert<T>(this IDbConnection db, string table, T ob...
    method Select (line 71) | public static List<T> Select<T>(this IDbConnection db, string table,
    method Update (line 77) | public static int Update<T>(this IDbConnection db, string table, T ite...

FILE: test/TestBase/ScenarioFor.cs
  class ScenarioFor (line 9) | public class ScenarioFor<TAggregateRoot> where TAggregateRoot : IAggrega...
    method ScenarioFor (line 14) | public ScenarioFor(Func<TAggregateRoot> constructor) => _aggregateRoot...
    method When (line 16) | public ScenarioFor<TAggregateRoot> When(params Action<TAggregateRoot>[...
    method With (line 22) | public ScenarioFor<TAggregateRoot> With(Action<TAggregateRoot> act)
    method Then (line 28) | public void Then(params object[] events)
    method ThenThrows (line 34) | public void ThenThrows<TException>(string message = "") where TExcepti...

FILE: test/TestBase/ScenarioForExisting.cs
  class ScenarioForExisting (line 9) | public class ScenarioForExisting<TAggregateRoot> where TAggregateRoot : ...
    method Given (line 14) | public ScenarioForExisting<TAggregateRoot> Given(Func<TAggregateRoot> ...
    method When (line 21) | public ScenarioForExisting<TAggregateRoot> When(params Action<TAggrega...
    method With (line 27) | public ScenarioForExisting<TAggregateRoot> With(Action<TAggregateRoot>...
    method Then (line 33) | public ScenarioForExisting<TAggregateRoot> Then(params object[] events)
    method AlsoAssert (line 45) | public ScenarioForExisting<TAggregateRoot> AlsoAssert(Func<TAggregateR...
    method ThenNone (line 51) | public void ThenNone()
    method ThenThrows (line 62) | public void ThenThrows<TException>(string message = null) where TExcep...

FILE: test/TestBase/SpecBase.cs
  class SpecBase (line 8) | public abstract class SpecBase<TAggregate> : SpecBase where TAggregate :...
    method ScenarioForExisting (line 10) | protected virtual ScenarioForExisting<TAggregate> ScenarioForExisting()
    method ScenarioFor (line 15) | protected ScenarioFor<TAggregate> ScenarioFor(Func<TAggregate> constru...
    method ScenarioForExisting (line 23) | protected virtual ScenarioForExisting<TAggregate> ScenarioForExisting<...
    method ScenarioFor (line 28) | protected virtual ScenarioFor<TAggregate> ScenarioFor<TAggregate>(Func...
    method Random (line 35) | protected T Random<T>()
  class SpecBase (line 21) | public abstract class SpecBase
    method ScenarioForExisting (line 10) | protected virtual ScenarioForExisting<TAggregate> ScenarioForExisting()
    method ScenarioFor (line 15) | protected ScenarioFor<TAggregate> ScenarioFor(Func<TAggregate> constru...
    method ScenarioForExisting (line 23) | protected virtual ScenarioForExisting<TAggregate> ScenarioForExisting<...
    method ScenarioFor (line 28) | protected virtual ScenarioFor<TAggregate> ScenarioFor<TAggregate>(Func...
    method Random (line 35) | protected T Random<T>()

FILE: test/TestBase/TestBaseWithInMemoryDb.cs
  class TestBaseWithInMemoryDb (line 5) | public abstract class TestBaseWithInMemoryDb : TestBaseWithIoC, IClassFi...
    method TestBaseWithInMemoryDb (line 10) | protected TestBaseWithInMemoryDb(InMemoryDatabaseFixture inMemoryDatab...
    method Dispose (line 15) | public override void Dispose()

FILE: test/TestBase/TestBaseWithIoC.cs
  class TestBaseWithIoC (line 12) | public abstract class TestBaseWithIoC : IDisposable
    method TestBaseWithIoC (line 17) | protected TestBaseWithIoC() => _services = new ServiceCollection();
    method Dispose (line 27) | public virtual void Dispose()
    method ConfigureServices (line 31) | protected TestBaseWithIoC ConfigureServices(Action<IServiceCollection>...
    method Build (line 37) | public void Build()
    method GetRequiredService (line 42) | protected T GetRequiredService<T>() => _localResolver.GetRequiredServi...
    method Fake (line 44) | protected virtual T Fake<T>(bool strict = false) where T : class
    method FakeAndRegister (line 55) | protected virtual T FakeAndRegister<T>(bool strict = false)
    method FakeAndRegisterWithType (line 65) | protected virtual T FakeAndRegisterWithType<T>(bool strict = false)
    method Verify (line 75) | protected virtual void Verify(Expression<Action> callSpecification, in...
    method Stub (line 80) | protected virtual void Stub<T>(Expression<Func<T>> callSpecification, ...
    method Ignore (line 85) | protected virtual T Ignore<T>() => A<T>.Ignored;
    method Matches (line 87) | protected T Matches<T>(Expression<Func<T, bool>> predicate) => A<T>.Th...
    method Random (line 91) | protected T Random<T>()
Condensed preview — 89 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (126K chars).
[
  {
    "path": ".gitignore",
    "chars": 589,
    "preview": " Common IntelliJ Platform excludes\n\n# User specific\n\n.idea/\n\n**/.idea/**/workspace.xml\n**/.idea/**/tasks.xml\n**/.idea/sh"
  },
  {
    "path": "Directory.build.props",
    "chars": 131,
    "preview": "<Project>\n    <PropertyGroup>\n        <Version>0.0.1</Version>\n\t\t<LangVersion>latest</LangVersion>\n    </PropertyGroup>\n"
  },
  {
    "path": "LICENSE",
    "chars": 11327,
    "preview": "    Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/li"
  },
  {
    "path": "README.md",
    "chars": 1989,
    "preview": "## Experimental Product Domain Based On Hexagonal Architecture Principles\nThis project is a sample application built usi"
  },
  {
    "path": "aspnet-core-clean-arch.sln",
    "chars": 5485,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"src\","
  },
  {
    "path": "src/Application/Application.csproj",
    "chars": 464,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netstandard2.1</TargetFramework>\n    </"
  },
  {
    "path": "src/Application/ICommandHandler.cs",
    "chars": 218,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\n\nnamespace Application\n{\n    public interface ICommandHandler<in T"
  },
  {
    "path": "src/Application/IEventHandler.cs",
    "chars": 212,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\n\nnamespace Application\n{\n    public interface IEventHandler <in TE"
  },
  {
    "path": "src/Application/IQueryHandler.cs",
    "chars": 234,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\n\nnamespace Application\n{\n    public interface IQueryHandler<in TMe"
  },
  {
    "path": "src/Application/UseCases/AddContentToProduct/AddContentToProductCommand.cs",
    "chars": 682,
    "preview": "using System;\n\nnamespace Application.UseCases.AddContentToProduct\n{\n    public class AddContentToProductCommand\n    {\n  "
  },
  {
    "path": "src/Application/UseCases/AddContentToProduct/AddContentToProductCommandHandler.cs",
    "chars": 921,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\nusing Domain.ProductContext;\n\nnamespace Application.UseCases.AddCo"
  },
  {
    "path": "src/Application/UseCases/CreateProduct/CreateProductCommand.cs",
    "chars": 438,
    "preview": "namespace Application.UseCases.CreateProduct\n{\n    public class CreateProductCommand\n    {\n        public CreateProductC"
  },
  {
    "path": "src/Application/UseCases/CreateProduct/CreateProductCommandHandler.cs",
    "chars": 788,
    "preview": "using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Domain.ProductContext;\n\nnamespace Application."
  },
  {
    "path": "src/Domain/Abstraction/AggregateRoot.cs",
    "chars": 4686,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\n\nnamespace Domain\n{\n    publ"
  },
  {
    "path": "src/Domain/Abstraction/BaseDomainEvent.cs",
    "chars": 118,
    "preview": "namespace Domain.Abstraction\n{\n    public class BaseDomainEvent\n    {\n        public int Version { get; set; }\n    }\n}"
  },
  {
    "path": "src/Domain/Abstraction/Entity.cs",
    "chars": 693,
    "preview": "using System;\n\nnamespace Domain\n{\n    public abstract class Entity \n    {\n        private readonly InstanceEventRouter "
  },
  {
    "path": "src/Domain/Abstraction/IRepository.cs",
    "chars": 535,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\n\nnamespace Domain.Abstraction\n{\n    /// <summary>\n    /// Provides"
  },
  {
    "path": "src/Domain/Abstraction/InstanceEventRouter.cs",
    "chars": 2866,
    "preview": "using System;\nusing System.Collections.Generic;\n\nnamespace Domain\n{\n    /// <summary>\n    ///     Routes an event to a "
  },
  {
    "path": "src/Domain/Abstraction/ValueObject.cs",
    "chars": 2696,
    "preview": "using System;\nusing System.Linq;\n\nnamespace Domain\n{\n      //Inspired from https://blogs.msdn.microsoft.com/cesardelator"
  },
  {
    "path": "src/Domain/BusinessException.cs",
    "chars": 371,
    "preview": "using System;\n\nnamespace Domain\n{\n    public class BusinessException : Exception\n    {\n        public BusinessException("
  },
  {
    "path": "src/Domain/Domain.csproj",
    "chars": 147,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netstandard2.1</TargetFramework>\n    </"
  },
  {
    "path": "src/Domain/ProductContext/AttributeRef.cs",
    "chars": 611,
    "preview": "namespace Domain.ProductContext\n{\n    public class AttributeRef : ValueObject<AttributeRef>\n    {\n        public Attribu"
  },
  {
    "path": "src/Domain/ProductContext/BrandRef.cs",
    "chars": 277,
    "preview": "namespace Domain.ProductContext\n{\n    public class BrandRef : ValueObject<BrandRef>\n    {\n        public BrandRef(int id"
  },
  {
    "path": "src/Domain/ProductContext/CategoryRef.cs",
    "chars": 286,
    "preview": "namespace Domain.ProductContext\n{\n    public class CategoryRef : ValueObject<CategoryRef>\n    {\n        public CategoryR"
  },
  {
    "path": "src/Domain/ProductContext/Content.cs",
    "chars": 1248,
    "preview": "using System.Collections.Generic;\nusing System.Linq;\nusing Domain.ProductContext.Events;\n\nnamespace Domain.ProductContex"
  },
  {
    "path": "src/Domain/ProductContext/Events/AttributeAddedToProduct.cs",
    "chars": 394,
    "preview": "using System;\n\nnamespace Domain.ProductContext.Events\n{\n    public class AttributeAddedToProduct : BaseProductEvent\n    "
  },
  {
    "path": "src/Domain/ProductContext/Events/BaseProductEvent.cs",
    "chars": 204,
    "preview": "using System;\nusing Domain.Abstraction;\n\nnamespace Domain.ProductContext.Events\n{\n    public abstract class BaseProductE"
  },
  {
    "path": "src/Domain/ProductContext/Events/ContentAddedToProduct.cs",
    "chars": 594,
    "preview": "using System;\n\nnamespace Domain.ProductContext.Events\n{\n    public class ContentAddedToProduct : BaseProductEvent\n    {\n"
  },
  {
    "path": "src/Domain/ProductContext/Events/ImageAddedToProduct.cs",
    "chars": 483,
    "preview": "using System;\n\nnamespace Domain.ProductContext.Events\n{\n    public class ImageAddedToProduct :BaseProductEvent\n    {\n   "
  },
  {
    "path": "src/Domain/ProductContext/Events/ProductApproved.cs",
    "chars": 272,
    "preview": "using System;\n\nnamespace Domain.ProductContext.Events\n{\n    public class ProductApproved : BaseProductEvent\n    {\n      "
  },
  {
    "path": "src/Domain/ProductContext/Events/ProductCreated.cs",
    "chars": 544,
    "preview": "using System;\n\nnamespace Domain.ProductContext.Events\n{\n    public class ProductCreated : BaseProductEvent\n    {\n       "
  },
  {
    "path": "src/Domain/ProductContext/Events/VariantAddedToProduct.cs",
    "chars": 641,
    "preview": "using System;\n\nnamespace Domain.ProductContext.Events\n{\n    public class VariantAddedToProduct: BaseProductEvent\n    {\n "
  },
  {
    "path": "src/Domain/ProductContext/IProductRepository.cs",
    "chars": 157,
    "preview": "using System;\nusing Domain.Abstraction;\n\nnamespace Domain.ProductContext\n{\n    public interface IProductRepository : IRe"
  },
  {
    "path": "src/Domain/ProductContext/ImageRef.cs",
    "chars": 366,
    "preview": "namespace Domain.ProductContext\n{\n    public class ImageRef : ValueObject<ImageRef>\n    {\n        public ImageRef(string"
  },
  {
    "path": "src/Domain/ProductContext/Product.cs",
    "chars": 5555,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Domain.ProductContext.Events;\n\nnamespace Domain"
  },
  {
    "path": "src/Domain/ProductContext/Variant.cs",
    "chars": 968,
    "preview": "using System.Collections.Generic;\nusing System.Linq;\nusing Domain.ProductContext.Events;\n\nnamespace Domain.ProductContex"
  },
  {
    "path": "src/Infrastructure/Infrastructure.csproj",
    "chars": 1123,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netstandard2.1</TargetFramework>\n    </"
  },
  {
    "path": "src/Infrastructure/Logging/WebHostExtensions.cs",
    "chars": 2100,
    "preview": "using System.Reflection;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.E"
  },
  {
    "path": "src/Infrastructure/Messaging/Mediator/IMediator.cs",
    "chars": 458,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\n\nnamespace Infrastructure.Messaging.Mediator\n{\n    public interfac"
  },
  {
    "path": "src/Infrastructure/Messaging/Mediator/Mediator.cs",
    "chars": 1614,
    "preview": "using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Application;\nusing Microsoft.Extensions.Depend"
  },
  {
    "path": "src/Infrastructure/Messaging/Mediator/ServiceCollectionExtensions.cs",
    "chars": 7929,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing Application;\nusing Inf"
  },
  {
    "path": "src/Infrastructure/Persistence/AggregateNotFoundException.cs",
    "chars": 123,
    "preview": "using System;\n\nnamespace Infrastructure.Persistence\n{\n    public class AggregateNotFoundException : Exception\n    {\n    "
  },
  {
    "path": "src/Infrastructure/Persistence/ConcurrencyException.cs",
    "chars": 285,
    "preview": "using System;\n\nnamespace Infrastructure.Persistence\n{\n    public class ConcurrencyException : Exception\n    {\n        pu"
  },
  {
    "path": "src/Infrastructure/Persistence/EventStoreProductRepository.cs",
    "chars": 950,
    "preview": "using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Domain.ProductContext;\n\nnamespace Infrastructu"
  },
  {
    "path": "src/Infrastructure/Persistence/IEventStore.cs",
    "chars": 324,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\n\nnamespace Infrastructure.Persistence\n{\n  "
  },
  {
    "path": "src/Infrastructure/Persistence/InMemory/EventStore/InMemoryEventStore.cs",
    "chars": 2950,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Infrastructure.Me"
  },
  {
    "path": "src/Infrastructure/Persistence/InMemory/ServiceCollectionExtensions.cs",
    "chars": 545,
    "preview": "using Domain.ProductContext;\nusing Infrastructure.Persistence.InMemory.EventStore;\nusing Microsoft.Extensions.Dependency"
  },
  {
    "path": "src/Infrastructure/Persistence/Mongo/EventStore/MongoDbEventStore.cs",
    "chars": 2596,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Infrastructure.Me"
  },
  {
    "path": "src/Infrastructure/Persistence/Mongo/IMongoDbContext.cs",
    "chars": 187,
    "preview": "using MongoDB.Driver;\n\nnamespace Infrastructure.Persistence.Mongo\n{\n    public interface IMongoDbContext\n    {\n        I"
  },
  {
    "path": "src/Infrastructure/Persistence/Mongo/MongoDbContext.cs",
    "chars": 460,
    "preview": "using MongoDB.Driver;\n\nnamespace Infrastructure.Persistence.Mongo\n{\n\n    public sealed class MongoDbContext : IMongoDbCo"
  },
  {
    "path": "src/Infrastructure/Persistence/Mongo/MongoDbProductRepository.cs",
    "chars": 436,
    "preview": "using System;\nusing Domain.ProductContext;\nusing Infrastructure.Messaging.Mediator;\n\nnamespace Infrastructure.Persistenc"
  },
  {
    "path": "src/Infrastructure/Persistence/Mongo/MongoDbProvider.cs",
    "chars": 1124,
    "preview": "using MongoDB.Driver;\n\nnamespace Infrastructure.Persistence.Mongo\n{\n    public class MongoDbProvider\n    {\n        publi"
  },
  {
    "path": "src/Infrastructure/Persistence/Mongo/MongoDbRepository.cs",
    "chars": 2654,
    "preview": "using System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Domain;\nusing Domain.Abstraction;\nusing I"
  },
  {
    "path": "src/Infrastructure/Persistence/Mongo/MongoSettings.cs",
    "chars": 557,
    "preview": "using System.Security.Authentication;\n\nnamespace Infrastructure.Persistence.Mongo\n{\n    public class MongoSettings\n    {"
  },
  {
    "path": "src/Infrastructure/Persistence/Mongo/ServiceCollectionExtensions.cs",
    "chars": 1439,
    "preview": "using Domain.ProductContext;\nusing Infrastructure.Persistence.Mongo.EventStore;\nusing Microsoft.Extensions.Configuration"
  },
  {
    "path": "src/Infrastructure/ServiceCollectionExtensions.cs",
    "chars": 666,
    "preview": "using Infrastructure.Persistence.InMemory;\nusing Infrastructure.Persistence.Mongo;\nusing Microsoft.Extensions.Configurat"
  },
  {
    "path": "src/WebApi/Extensions/ApplicationBuilderExtensions.cs",
    "chars": 2025,
    "preview": "using System.Reflection;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.H"
  },
  {
    "path": "src/WebApi/Extensions/OriginalNameCamelCaseContractResolver.cs",
    "chars": 1125,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Seriali"
  },
  {
    "path": "src/WebApi/Extensions/ServiceCollectionExtensions.cs",
    "chars": 1370,
    "preview": "using System;\nusing System.IO;\nusing System.Reflection;\nusing FluentValidation.AspNetCore;\nusing Microsoft.AspNetCore.Bu"
  },
  {
    "path": "src/WebApi/Program.cs",
    "chars": 728,
    "preview": "using System.Threading.Tasks;\nusing Infrastructure;\nusing Infrastructure.Logging;\nusing Microsoft.AspNetCore;\nusing Mic"
  },
  {
    "path": "src/WebApi/Properties/launchSettings.json",
    "chars": 775,
    "preview": "{\n  \"$schema\": \"http://json.schemastore.org/launchsettings.json\",\n  \"iisSettings\": {\n    \"windowsAuthentication\": false"
  },
  {
    "path": "src/WebApi/Startup.cs",
    "chars": 804,
    "preview": "using Infrastructure; \nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensio"
  },
  {
    "path": "src/WebApi/UseCases/AddContentToProduct/AddContentToProductCommandValidator.cs",
    "chars": 549,
    "preview": "using Application.UseCases.AddContentToProduct;\nusing FluentValidation;\n\nnamespace WebApi.UseCases.AddContentToProduct\n{"
  },
  {
    "path": "src/WebApi/UseCases/AddContentToProduct/ProductController.cs",
    "chars": 719,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\nusing Application.UseCases.AddContentToProduct;\nusing Infrastructu"
  },
  {
    "path": "src/WebApi/UseCases/CreateProduct/CreateProductCommandValidator.cs",
    "chars": 426,
    "preview": "using Application.UseCases.CreateProduct;\nusing FluentValidation;\n\nnamespace WebApi.UseCases.CreateProduct\n{\n    public "
  },
  {
    "path": "src/WebApi/UseCases/CreateProduct/ProductsController.cs",
    "chars": 701,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\nusing Application.UseCases.CreateProduct;\nusing Infrastructure.Mes"
  },
  {
    "path": "src/WebApi/WebApi.csproj",
    "chars": 901,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n    <PropertyGroup>\n        <TargetFramework>netcoreapp3.0</TargetFramework>\n   "
  },
  {
    "path": "src/WebApi/app.config",
    "chars": 129,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<configuration>\n  <runtime>\n    <gcServer enabled=\"true\"/>\n  </runtime>\n</confi"
  },
  {
    "path": "src/WebApi/appsettings.Development.json",
    "chars": 137,
    "preview": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Informat"
  },
  {
    "path": "src/WebApi/appsettings.Production.json",
    "chars": 97,
    "preview": "{\n  \"Serilog\": {\n    \"Console\": false,\n    \"MinimumLevel\": {\n      \"Default\": \"Error\"\n    }\n  }\n}"
  },
  {
    "path": "src/WebApi/appsettings.Staging.json",
    "chars": 97,
    "preview": "{\n  \"Serilog\": {\n    \"Console\": false,\n    \"MinimumLevel\": {\n      \"Default\": \"Error\"\n    }\n  }\n}"
  },
  {
    "path": "src/WebApi/appsettings.json",
    "chars": 414,
    "preview": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"Serilog\": {\n    \"GraylogIp\": \"\",\n    \"Graylo"
  },
  {
    "path": "test/Application.Tests/Application.Tests.csproj",
    "chars": 785,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netcoreapp3.0</TargetFramework>\n\n      "
  },
  {
    "path": "test/Application.Tests/CommandHandlerTestBase.cs",
    "chars": 403,
    "preview": "using Microsoft.Extensions.DependencyInjection;\nusing TestBase;\n\nnamespace Application.Tests\n{\n    public abstract class"
  },
  {
    "path": "test/Application.Tests/CreateProductCommandHandlerTests.cs",
    "chars": 1387,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\nusing Application.UseCases.CreateProduct;\nusing Domain.ProductCont"
  },
  {
    "path": "test/Domain.Specs/Domain.Specs.csproj",
    "chars": 624,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netcoreapp3.0</TargetFramework>\n\n      "
  },
  {
    "path": "test/Domain.Specs/ProductSpecs/WhenContentAddedToProduct.cs",
    "chars": 2979,
    "preview": "using System;\nusing System.Linq;\nusing Domain.ProductContext;\nusing Domain.ProductContext.Events;\nusing TestBase;\nusing "
  },
  {
    "path": "test/Domain.Specs/ProductSpecs/WhenCreated.cs",
    "chars": 859,
    "preview": "using System;\nusing Domain.ProductContext;\nusing Domain.ProductContext.Events;\nusing TestBase;\nusing Xunit;\n\nnamespace D"
  },
  {
    "path": "test/Domain.Specs/ProductSpecs/WhenProductApproved.cs",
    "chars": 3685,
    "preview": "using System;\nusing Domain.ProductContext;\nusing Domain.ProductContext.Events;\nusing TestBase;\nusing Xunit;\n\nnamespace D"
  },
  {
    "path": "test/Domain.Specs/ProductSpecs/WhenVariantAddedToProduct.cs",
    "chars": 2792,
    "preview": "using System;\nusing System.Linq;\nusing Domain.ProductContext;\nusing Domain.ProductContext.Events;\nusing TestBase;\nusing "
  },
  {
    "path": "test/Infrastructure.Tests/Infrastructure.Tests.csproj",
    "chars": 707,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netcoreapp3.0</TargetFramework>\n\n      "
  },
  {
    "path": "test/Infrastructure.Tests/MediatorTests.cs",
    "chars": 1450,
    "preview": "using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Application;\nusing Infrastructure.Messaging.Me"
  },
  {
    "path": "test/TestBase/InMemoryDatabaseFixture.cs",
    "chars": 2437,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Data;\nusing System.Linq.Expressions;\nusing ServiceStack.Orm"
  },
  {
    "path": "test/TestBase/ScenarioFor.cs",
    "chars": 1367,
    "preview": "using FluentAssertions;\nusing KellermanSoftware.CompareNetObjects;\nusing System;\nusing System.Linq;\nusing Domain;\n\nname"
  },
  {
    "path": "test/TestBase/ScenarioForExisting.cs",
    "chars": 2397,
    "preview": "using System;\nusing System.Linq;\nusing Domain;\nusing FluentAssertions;\nusing KellermanSoftware.CompareNetObjects;\n\nname"
  },
  {
    "path": "test/TestBase/SpecBase.cs",
    "chars": 1424,
    "preview": "using System;\nusing System.Linq;\nusing AutoFixture;\nusing Domain;\n\nnamespace TestBase\n{\n    public abstract class SpecB"
  },
  {
    "path": "test/TestBase/TestBase.csproj",
    "chars": 1180,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netcoreapp3.0</TargetFramework>\n\n      "
  },
  {
    "path": "test/TestBase/TestBaseWithInMemoryDb.cs",
    "chars": 529,
    "preview": "using Xunit;\n\nnamespace TestBase\n{\n    public abstract class TestBaseWithInMemoryDb : TestBaseWithIoC, IClassFixture<InM"
  },
  {
    "path": "test/TestBase/TestBaseWithIoC.cs",
    "chars": 3006,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing AutoFixture;\nusi"
  }
]

About this extraction

This page contains the full source code of the CanerPatir/aspnet-core-clean-arch GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 89 files (113.0 KB), approximately 26.6k tokens, and a symbol index with 271 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!