{"id":25389954,"url":"https://github.com/kayvansol/cqrs_dotnet6","last_synced_at":"2025-04-09T22:27:28.381Z","repository":{"id":277583310,"uuid":"932886285","full_name":"kayvansol/CQRS_DotNet6","owner":"kayvansol","description":"A .Net6 based Api Project with Clean Architecture, CQRS pattern, xUnit Tests, Single Sign On (SSO) with Duende Identity Server, Two Factor Authentication \u0026 RabbitMQ","archived":false,"fork":false,"pushed_at":"2025-03-29T19:27:18.000Z","size":12184,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-29T20:19:49.470Z","etag":null,"topics":["authorizationfilter","automapper","clean-architecture","cqrs-pattern","docker-container","dotnet","duende-identityserver","entity-framework-core","fluent-validation","hangfire","hosted-services","mediatr","mvc","rabbitmq","seq","serilog-sink","sql-server","swagger","two-factor-authentication","xunit"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kayvansol.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-14T17:47:09.000Z","updated_at":"2025-03-29T19:27:21.000Z","dependencies_parsed_at":"2025-03-02T21:22:36.237Z","dependency_job_id":"f2a649b3-4686-4e2e-8b63-aad6b346534a","html_url":"https://github.com/kayvansol/CQRS_DotNet6","commit_stats":null,"previous_names":["kayvansol/cqrs_dotnet6"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kayvansol%2FCQRS_DotNet6","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kayvansol%2FCQRS_DotNet6/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kayvansol%2FCQRS_DotNet6/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kayvansol%2FCQRS_DotNet6/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kayvansol","download_url":"https://codeload.github.com/kayvansol/CQRS_DotNet6/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248121994,"owners_count":21051218,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorizationfilter","automapper","clean-architecture","cqrs-pattern","docker-container","dotnet","duende-identityserver","entity-framework-core","fluent-validation","hangfire","hosted-services","mediatr","mvc","rabbitmq","seq","serilog-sink","sql-server","swagger","two-factor-authentication","xunit"],"created_at":"2025-02-15T14:23:24.604Z","updated_at":"2025-04-09T22:27:28.372Z","avatar_url":"https://github.com/kayvansol.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dot Net \u0026 CQRS\n\u003cbr /\u003e\nCQRS Pattern \u0026 Clean Architecture Design In Dot Net 6.0 based Api Project with Test units in xUnit tool \u0026 Duende Identity Server with Two Factor Authentication \u0026 RabbitMQ\n\u003cbr /\u003e\u003cbr /\u003e\n\n**Command Query Responsibility Segregation (CQRS)** is a design pattern that segregates read and write operations for a data store into separate data models. This approach allows each model to be optimized independently and can improve the performance, scalability, and security of an application.\n\u003cbr /\u003e\u003cbr /\u003e\n\n# Project Structure :\n\n**Clean Architecture** is an architecture pattern aimed at building applications that we can maintain, scale, and test easily.\nIt achieves this by separating the application into different layers that have distinct responsibilities:\n\n**Domain** Layer – The domain layer represents the application’s core business rules and entities. This is the innermost layer and should not have any external dependencies. \n\n**Application** Layer – The application layer sits just outside the domain layer and acts as an intermediary between the domain layer and other layers. In other words, it contains the use cases of the application and we expose the core business rules of the domain layer through the application layer. This layer depends just on the domain layer.\n\n**Infrastructure** Layer – We implement all the external services like databases, Queues, file storage, emails, etc. in the infrastructure layer. It contains the implementations of the interfaces defined in the domain layer.\n\n**Presentation** Layer – The presentation layer handles the user interactions and fetches data to the user interface.\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/structure.png?raw=true)\n\n\u003chr /\u003e\n\n# Databases :\n\nYou can create the databases from scripts placed inside **Store.Infra.Sql** project :\n```\nStoreDatabaseScript.sql\nLogDbDatabaseScript.sql\n```\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/Databases.png?raw=true)\n\n# ERD\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/ERD.png?raw=true)\n\nRun Line below inside **Store.Infra.Sql** project to generate the **StoreContext** :\n```\nScaffold-DbContext \"Data Source=.;Initial Catalog=Store;Integrated Security=True;TrustServerCertificate=True\" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Context -Force\n```\n\nFor the **LogDbContext** :\n```\nScaffold-DbContext \"Data Source=.;Initial Catalog=LogDB;Integrated Security=True;TrustServerCertificate=True\" Microsoft.EntityFrameworkCore.SqlServer -OutputDir LogContext -Force\n```\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/Context.png?raw=true)\n\n\u003chr /\u003e\n\n# Rest Apis Swagger page :\n\nSimplify API development with open-source and professional tools, built to help you and your team efficiently design and document APIs at scale.\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/swagger.png?raw=true)\n\n\u003chr /\u003e\n\n# Order Command Handler Code (for e.g.):\n\n```\n    public class AddOrderCommandDto\n    {\n        public int? CustomerId { get; set; }\n\n        public byte OrderStatus { get; set; }\n\n        public DateTime OrderDate { get; set; } \n\n        public DateTime RequiredDate { get; set; }\n\n        public DateTime? ShippedDate { get; set; }\n\n        public List\u003cItems\u003e Items { get; set; } = new List\u003cItems\u003e();\n\n    }\n\n    public class Items\n    {\n        public int ProductId { get; set; }\n\n        public int Quantity { get; set; }\n\n        public decimal Price { get; set; }\n\n        public decimal Discount { get; set; }\n\n    }\n```\n\n```\n    public record AddOrderCommand(AddOrderCommandDto AddDto) : IRequest\u003cResultDto\u003cUnit\u003e\u003e\n    {\n\n    }\n```\n```\n    public class OrderRepository : Repository\u003cOrder, int\u003e, IOrderRepository\n    {\n\n        private readonly StoreContext _context;\n        private readonly IRepository\u003cOrder, int\u003e _repo;\n        private readonly IMapper _mapper;\n\n        public OrderRepository(StoreContext context,\n            IRepository\u003cOrder, int\u003e repo, IMapper mapper) : base(context)\n        {\n            _context = context;\n            _repo = repo;\n            _mapper = mapper;\n        }\n\n        public async Task\u003cOrder\u003e CreateAsync(Order data)\n        {\n            _context.Add(data);\n            _context.SaveChanges();\n\n            return data;\n        }\n\n    }\n```\n\n```\n    public class AddOrderCommandHandler : IRequestHandler\u003cAddOrderCommand, ResultDto\u003cUnit\u003e\u003e\n    {\n        private readonly IOrderRepository orderRepository;\n        private readonly IMapper mapper;\n\n        public AddOrderCommandHandler(IOrderRepository orderRepository, IMapper mapper)\n        {\n            this.orderRepository = orderRepository;\n            this.mapper = mapper;\n        }\n\n        public async Task\u003cResultDto\u003cUnit\u003e\u003e Handle(AddOrderCommand request, CancellationToken cancellationToken)\n        {\n            var dto = request.AddDto;\n\n            List\u003cDomain.OrderItem\u003e orderItems = new List\u003cDomain.OrderItem\u003e();\n\n            int i = 0;\n\n            foreach (var item in dto.Items)\n            {\n                i++;\n\n                orderItems.Add(new Domain.OrderItem\n                {\n                    Discount = item.Discount,\n                    Price = item.Price,\n                    ProductId = item.ProductId,\n                    Quantity = item.Quantity,\n                    ItemId = i                    \n                });\n            }\n\n            Domain.Order order = new()\n            {\n                CustomerId = dto.CustomerId,\n                OrderDate = dto.OrderDate,\n                RequiredDate = dto.RequiredDate,\n                ShippedDate = dto.ShippedDate,\n                OrderStatus = dto.OrderStatus,\n                OrderItems = orderItems\n            };\n\n            await orderRepository.CreateAsync(order);\n\n            // Send order event success action message to RabbitMQ ...\n            Producer.Produce($\"Order with number {order.OrderId} created for customer number {order.CustomerId}\");\n\n            return ResultDto\u003cUnit\u003e.ReturnData(Unit.Value, (int)EnumResponseStatus.OK, (int)EnumResultCode.Success, EnumResultCode.Success.GetDisplayName());\n        }\n    }\n```\n\n```\n    public class BaseController : Controller\n    {\n        private IMediator mediator;\n\n        protected IMediator Mediator =\u003e mediator ??= HttpContext.RequestServices.GetService\u003cIMediator\u003e();\n    }\n```\n\n```\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class OrderController : BaseController\n    {\n        [AllowAnonymous]\n        [HttpPost(\"InsertOrder\")]\n        public async Task\u003cResultDto\u003cUnit\u003e\u003e InsertOrder(AddOrderCommand command, CancellationToken cancellationToken) =\u003e await Mediator.Send(command, cancellationToken);\n\n    }\n```\n\n\u003chr /\u003e\n\n# Test Class with xUnit :\n\n**xUnit** is a free, open source, community-focused unit testing tool for the .NET Framework.\n\n```\n    public class CategoryTestData\n    {\n\n        public static IEnumerable\u003cobject[]\u003e addCategoryCommandDto()\n        {\n            List\u003cAddCategoryCommandDto[]\u003e data = new List\u003cAddCategoryCommandDto[]\u003e();\n\n            AddCategoryCommandDto[] adds = new AddCategoryCommandDto[1];\n\n            adds[0] = new AddCategoryCommandDto()\n            {\n                CategoryName = \"Dell G9 Server\"\n            };\n\n            data.Add(adds);\n\n            return data;\n                        \n        }\n        \n    }\n```\n```\n    public class CategoryTests : IDisposable\n    {\n\n        private readonly ICategoryRepository _repository;\n        private readonly AddCategoryCommandHandler _handler;\n        private readonly Repository\u003cDomain.Category, int\u003e _repo;\n        private readonly StoreContext _context;\n        private readonly IMapper _mapper;\n\n        public CategoryTests()\n        {\n            _mapper = GetServices.GetMapper();\n            _context = GetServices.GetStoreContext();\n            _repo = new Repository\u003cDomain.Category, int\u003e(_context);\n            _repository = new CategoryRepository(_context, _repo, _mapper);\n            _handler = new AddCategoryCommandHandler(_repository, _mapper);\n        }\n\n\n        [Theory]\n        [MemberData(nameof(CategoryTestData.addCategoryCommandDto), MemberType = typeof(CategoryTestData))]\n        public async Task Post_create_a_new_category_and_response_status_code_ok(AddCategoryCommandDto dto)\n        {\n            // Arrange\n            var request = new AddCategoryCommand(dto);\n            var token = new CancellationToken(false);\n\n            // Act\n            var result = await _handler.Handle(request, token);\n\n            // Assert       \n            result.StatusCode.Should().Be((int)HttpStatusCode.OK);\n\n            result.Data.Should().NotBeNull();\n\n        }\n\n        public void Dispose()\n        {                        \n            _repo.Dispose();            \n            _context.Dispose();\n        }\n\n    }\n```\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/test.png?raw=true)\n\n\u003chr /\u003e\n\n# Single Sign On (SSO) with Duende Identity Server :\n\nThe most flexible and standards-compliant **OpenID Connect** and **OAuth 2.0** framework for ASP.NET Core.\n\n**Migrations** to use SQL Server for User Registeration and Login process :\n```\nadd-Migration init -Context ApplicationDbContext\n\nUpdate-Database -Context ApplicationDbContext\n\n\nadd-Migration init -Context ConfigurationDbContext\n\nUpdate-Database -Context ConfigurationDbContext\n\n\nadd-Migration init -Context PersistedGrantDbContext\n\nUpdate-Database -Context PersistedGrantDbContext\n```\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/Migrations.png?raw=true)\n\nRunning line below inside ConfigurePipeline at HostingExtensions.cs to insert some initial data in the identity database :\n```\nInitializeDbTestData(app);\n```\n\n**Identity DataBase** :\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/StoreAccount.png?raw=true)\n\n**Register a new User** :\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/Register.png?raw=true)\n\n**Login** :\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/identity.png?raw=true)\n\n**Two Factor Authentication** :\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/TwoFactor.png?raw=true)\n\n**Swagger Authentication** :\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/identity3.png?raw=true)\n\nDuende Identity Server **Admin** Page :\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/identity2.png?raw=true)\n\n\u003chr /\u003e\n\n**Identity Logging** :\n\n**Seq** is the self-hosted search, analysis, and alerting server built for structured log data.\n\nSink Duende Identity Server **Event Logs** to the avaiable Seq Server on Docker :\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/seq.png?raw=true)\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/seq1.png?raw=true)\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/seq2.png?raw=true)\n\n\u003chr /\u003e\n\n# Event Store :\n\n**RabbitMQ with Docker** :\n\n```\ndocker pull rabbitmq:4.0.7-management\n\ndocker run -d --hostname myrabbit --name rabbit -p 5672:5672 -p 5673:5673 -p 15672:15672 rabbitmq:4.0.7-management\n```\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/rabbitContainer.png?raw=true)\n\n\u003chr /\u003e\n\nOrder Message has been sent to RabbitMQ :\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/rabbit.png?raw=true)\n\n\u003chr /\u003e\n\n# Hangfire Dashbourd :\n\nAn easy way to perform **background processing** in .NET and .NET Core applications. No Windows Service or separate process required.\n\n![alt text](https://raw.githubusercontent.com/kayvansol/CQRS_DotNet6/refs/heads/main/img/hangfire.png?raw=true)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkayvansol%2Fcqrs_dotnet6","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkayvansol%2Fcqrs_dotnet6","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkayvansol%2Fcqrs_dotnet6/lists"}