Repository: BehzadDara/OnlineShop
Branch: master
Commit: bcf4372f0d9f
Files: 239
Total size: 300.1 MB
Directory structure:
gitextract__0w2wh4p/
├── .dockerignore
├── .gitattributes
├── .gitignore
├── ApiGateways/
│ ├── OcelotApiGateway/
│ │ ├── Dockerfile
│ │ ├── OcelotApiGateway.csproj
│ │ ├── Program.cs
│ │ ├── Properties/
│ │ │ └── launchSettings.json
│ │ ├── appsettings.Development.json
│ │ ├── appsettings.json
│ │ ├── ocelot.Development.json
│ │ ├── ocelot.Local.json
│ │ └── ocelot.json
│ └── OnlineShop.Aggregator/
│ └── OnlineShop.Aggregator/
│ ├── Controllers/
│ │ └── ShoppingController.cs
│ ├── DTOs/
│ │ ├── BasketDTO.cs
│ │ ├── BasketItemExtendedDTO.cs
│ │ ├── CatalogDTO.cs
│ │ ├── OrderResponseDTO.cs
│ │ └── ShoppingDTO.cs
│ ├── Dockerfile
│ ├── Extensions/
│ │ └── HttpClientExtensions.cs
│ ├── OnlineShop.Aggregator.csproj
│ ├── OnlineShop.Aggregator.http
│ ├── Program.cs
│ ├── Properties/
│ │ └── launchSettings.json
│ ├── Services/
│ │ ├── BasketService.cs
│ │ ├── CatalogService.cs
│ │ ├── IBasketService.cs
│ │ ├── ICatalogService.cs
│ │ ├── IOrderService.cs
│ │ └── OrderService.cs
│ ├── appsettings.Development.json
│ └── appsettings.json
├── BuildingBlocks/
│ └── EventBus.Messages/
│ ├── Common/
│ │ └── EventBusConstant.cs
│ ├── EventBus.Messages.csproj
│ └── Events/
│ ├── BasketCheckoutEvent.cs
│ └── IntegrationBaseEvent.cs
├── OnlineShop.sln
├── README.md
├── Services/
│ ├── Basket/
│ │ └── Basket.Api/
│ │ ├── Basket.Api.csproj
│ │ ├── Basket.Api.http
│ │ ├── Controllers/
│ │ │ └── OrderController.cs
│ │ ├── Dockerfile
│ │ ├── Entities/
│ │ │ ├── BasketCheckout.cs
│ │ │ ├── Order.cs
│ │ │ └── OrderItem.cs
│ │ ├── GrpcServices/
│ │ │ └── DiscountGrpcService.cs
│ │ ├── Mapper/
│ │ │ └── BasketProfile.cs
│ │ ├── Program.cs
│ │ ├── Properties/
│ │ │ └── launchSettings.json
│ │ ├── Repositories/
│ │ │ ├── IOrderRepository.cs
│ │ │ └── OrderRepository.cs
│ │ ├── appsettings.Development.json
│ │ └── appsettings.json
│ ├── Catalog/
│ │ └── Catalog.Api/
│ │ ├── Catalog.Api.csproj
│ │ ├── Catalog.Api.http
│ │ ├── Controllers/
│ │ │ └── ProductController.cs
│ │ ├── Data/
│ │ │ ├── CatalogContext.cs
│ │ │ ├── CatalogContextSeed.cs
│ │ │ └── ICatalogContext.cs
│ │ ├── Dockerfile
│ │ ├── Entities/
│ │ │ └── Product.cs
│ │ ├── Products.Json
│ │ ├── Program.cs
│ │ ├── Properties/
│ │ │ └── launchSettings.json
│ │ ├── Repositories/
│ │ │ ├── IProductRepository.cs
│ │ │ └── ProductRepository.cs
│ │ ├── appsettings.Development.json
│ │ └── appsettings.json
│ ├── Discount/
│ │ ├── Discount.Api/
│ │ │ ├── Controllers/
│ │ │ │ └── CouponController.cs
│ │ │ ├── Discount.Api.csproj
│ │ │ ├── Discount.Api.http
│ │ │ ├── Dockerfile
│ │ │ ├── Entities/
│ │ │ │ └── Coupon.cs
│ │ │ ├── Extensions/
│ │ │ │ └── HostExtensions.cs
│ │ │ ├── Program.cs
│ │ │ ├── Properties/
│ │ │ │ └── launchSettings.json
│ │ │ ├── Repositories/
│ │ │ │ ├── CouponRepository.cs
│ │ │ │ └── ICouponRepository.cs
│ │ │ ├── appsettings.Development.json
│ │ │ └── appsettings.json
│ │ └── Discount.Grpc/
│ │ ├── Discount.Grpc.csproj
│ │ ├── Dockerfile
│ │ ├── Entities/
│ │ │ └── Coupon.cs
│ │ ├── Extensions/
│ │ │ └── HostExtensions.cs
│ │ ├── Mapper/
│ │ │ └── DiscountProfile.cs
│ │ ├── Program.cs
│ │ ├── Properties/
│ │ │ └── launchSettings.json
│ │ ├── Protos/
│ │ │ └── discount.proto
│ │ ├── Repositories/
│ │ │ ├── CouponRepository.cs
│ │ │ └── ICouponRepository.cs
│ │ ├── Services/
│ │ │ └── DiscountService.cs
│ │ ├── appsettings.Development.json
│ │ └── appsettings.json
│ └── Ordering/
│ ├── Ordering.Api/
│ │ ├── Controllers/
│ │ │ └── OrderController.cs
│ │ ├── Dockerfile
│ │ ├── EventBusConsumer/
│ │ │ └── BasketCheckoutConsumer.cs
│ │ ├── HostExtensions.cs
│ │ ├── Mapper/
│ │ │ └── CheckoutProfile.cs
│ │ ├── Ordering.Api.csproj
│ │ ├── Ordering.Api.http
│ │ ├── Program.cs
│ │ ├── Properties/
│ │ │ └── launchSettings.json
│ │ ├── appsettings.Development.json
│ │ └── appsettings.json
│ ├── Ordering.Application/
│ │ ├── ApplicationServiceRegistration.cs
│ │ ├── Behaviors/
│ │ │ ├── UnhandledExceptionBehavior.cs
│ │ │ └── ValidationBehavior.cs
│ │ ├── Contracts/
│ │ │ ├── Infrastructure/
│ │ │ │ └── IEmailService.cs
│ │ │ └── Persistence/
│ │ │ ├── IAsyncRepository.cs
│ │ │ └── IOrderRepository.cs
│ │ ├── Exceptions/
│ │ │ ├── NotFoundException.cs
│ │ │ └── ValidationException.cs
│ │ ├── Features/
│ │ │ └── Orders/
│ │ │ ├── Commands/
│ │ │ │ ├── CreateOrder/
│ │ │ │ │ ├── CreateOrderCommand.cs
│ │ │ │ │ ├── CreateOrderCommandHandler.cs
│ │ │ │ │ └── CreateOrderCommandValidator.cs
│ │ │ │ ├── DeleteOrder/
│ │ │ │ │ ├── DeleteOrderCommand.cs
│ │ │ │ │ └── DeleteOrderCommandHandler.cs
│ │ │ │ └── UpdateOrder/
│ │ │ │ ├── UpdateOrderCommand.cs
│ │ │ │ ├── UpdateOrderCommandHandler.cs
│ │ │ │ └── UpdateOrderCommandValidator.cs
│ │ │ └── Queries/
│ │ │ └── GetOrdersList/
│ │ │ ├── GetOrdersListQuery.cs
│ │ │ ├── GetOrdersListQueryHandler.cs
│ │ │ └── OrderViewModel.cs
│ │ ├── Mappings/
│ │ │ └── MappingProfile.cs
│ │ ├── Models/
│ │ │ ├── Email.cs
│ │ │ └── EmailSetting.cs
│ │ └── Ordering.Application.csproj
│ ├── Ordering.Domain/
│ │ ├── Common/
│ │ │ ├── EntityBase.cs
│ │ │ └── ValueObject.cs
│ │ ├── Entities/
│ │ │ └── Order.cs
│ │ └── Ordering.Domain.csproj
│ └── Ordering.Infrastructure/
│ ├── InfrastructureServiceRegistration.cs
│ ├── Migrations/
│ │ ├── 20231211200458_Init.Designer.cs
│ │ ├── 20231211200458_Init.cs
│ │ └── OrderDBContextModelSnapshot.cs
│ ├── Ordering.Infrastructure.csproj
│ ├── Persistence/
│ │ ├── OrderDBContext.cs
│ │ └── OrderDBContextSeed.cs
│ ├── Proxies/
│ │ └── EmailService.cs
│ └── Repositories/
│ ├── OrderRepository.cs
│ └── RepositoryBase.cs
├── docker-compose.dcproj
├── docker-compose.override.yml
├── docker-compose.yml
├── launchSettings.json
└── mongo_data/
├── WiredTiger
├── WiredTiger.turtle
├── WiredTiger.wt
├── WiredTigerHS.wt
├── _mdb_catalog.wt
├── collection-0--2162413245845826319.wt
├── collection-2--2162413245845826319.wt
├── collection-4--2162413245845826319.wt
├── collection-7--2162413245845826319.wt
├── diagnostic.data/
│ ├── metrics.2023-12-05T22-35-19Z-00000
│ ├── metrics.2023-12-05T22-54-37Z-00000
│ ├── metrics.2023-12-05T22-56-43Z-00000
│ ├── metrics.2023-12-05T22-58-50Z-00000
│ ├── metrics.2023-12-05T23-02-35Z-00000
│ ├── metrics.2023-12-05T23-04-34Z-00000
│ ├── metrics.2023-12-06T09-50-37Z-00000
│ ├── metrics.2023-12-06T11-17-57Z-00000
│ ├── metrics.2023-12-06T11-45-54Z-00000
│ ├── metrics.2023-12-06T11-47-38Z-00000
│ ├── metrics.2023-12-06T11-49-51Z-00000
│ ├── metrics.2023-12-06T11-51-33Z-00000
│ ├── metrics.2023-12-06T12-01-34Z-00000
│ ├── metrics.2023-12-06T12-22-29Z-00000
│ ├── metrics.2023-12-06T12-25-10Z-00000
│ ├── metrics.2023-12-06T13-14-10Z-00000
│ ├── metrics.2023-12-06T22-44-00Z-00000
│ ├── metrics.2023-12-07T10-00-48Z-00000
│ ├── metrics.2023-12-07T10-42-49Z-00000
│ ├── metrics.2023-12-09T20-35-09Z-00000
│ ├── metrics.2023-12-09T20-52-53Z-00000
│ ├── metrics.2023-12-09T21-12-07Z-00000
│ ├── metrics.2023-12-09T21-22-39Z-00000
│ ├── metrics.2023-12-10T08-52-41Z-00000
│ ├── metrics.2023-12-10T08-53-11Z-00000
│ ├── metrics.2023-12-10T09-02-16Z-00000
│ ├── metrics.2023-12-10T09-03-51Z-00000
│ ├── metrics.2023-12-10T09-10-11Z-00000
│ ├── metrics.2023-12-10T09-12-08Z-00000
│ ├── metrics.2023-12-10T09-12-44Z-00000
│ ├── metrics.2023-12-10T09-14-28Z-00000
│ ├── metrics.2023-12-10T09-22-38Z-00000
│ ├── metrics.2023-12-10T09-24-18Z-00000
│ ├── metrics.2023-12-10T09-26-25Z-00000
│ ├── metrics.2023-12-10T09-27-56Z-00000
│ ├── metrics.2023-12-10T09-31-41Z-00000
│ ├── metrics.2023-12-10T09-34-53Z-00000
│ ├── metrics.2023-12-10T10-05-22Z-00000
│ ├── metrics.2023-12-10T10-06-22Z-00000
│ ├── metrics.2023-12-10T10-12-00Z-00000
│ ├── metrics.2023-12-10T10-20-34Z-00000
│ ├── metrics.2023-12-10T10-27-09Z-00000
│ ├── metrics.2023-12-10T12-12-15Z-00000
│ ├── metrics.2023-12-10T13-36-47Z-00000
│ ├── metrics.2023-12-10T13-43-27Z-00000
│ ├── metrics.2023-12-10T20-59-24Z-00000
│ ├── metrics.2023-12-11T12-23-14Z-00000
│ ├── metrics.2023-12-11T12-30-27Z-00000
│ ├── metrics.2023-12-11T13-08-05Z-00000
│ ├── metrics.2023-12-11T14-37-06Z-00000
│ ├── metrics.2023-12-11T19-48-10Z-00000
│ ├── metrics.2023-12-11T19-48-31Z-00000
│ ├── metrics.2023-12-12T08-42-51Z-00000
│ ├── metrics.2023-12-12T08-46-13Z-00000
│ ├── metrics.2023-12-12T08-49-01Z-00000
│ ├── metrics.2023-12-12T09-02-14Z-00000
│ ├── metrics.2023-12-12T12-16-33Z-00000
│ ├── metrics.2023-12-12T12-34-54Z-00000
│ ├── metrics.2023-12-12T12-37-21Z-00000
│ ├── metrics.2023-12-12T12-40-44Z-00000
│ ├── metrics.2023-12-12T12-45-56Z-00000
│ ├── metrics.2023-12-12T13-11-29Z-00000
│ ├── metrics.2023-12-12T13-14-47Z-00000
│ ├── metrics.2023-12-12T13-16-13Z-00000
│ ├── metrics.2023-12-12T13-17-38Z-00000
│ ├── metrics.2023-12-12T14-53-07Z-00000
│ ├── metrics.2023-12-13T09-56-35Z-00000
│ ├── metrics.2023-12-13T10-54-21Z-00000
│ ├── metrics.2023-12-13T10-56-58Z-00000
│ ├── metrics.2023-12-13T11-45-51Z-00000
│ ├── metrics.2023-12-13T11-46-22Z-00000
│ ├── metrics.2023-12-16T16-22-45Z-00000
│ ├── metrics.2023-12-17T14-03-32Z-00000
│ ├── metrics.2023-12-17T15-15-10Z-00000
│ └── metrics.2023-12-17T20-40-29Z-00000
├── index-1--2162413245845826319.wt
├── index-3--2162413245845826319.wt
├── index-5--2162413245845826319.wt
├── index-6--2162413245845826319.wt
├── index-8--2162413245845826319.wt
├── journal/
│ ├── WiredTigerLog.0000000075
│ ├── WiredTigerPreplog.0000000001
│ └── WiredTigerPreplog.0000000002
├── sizeStorer.wt
└── storage.bson
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
================================================
FILE: ApiGateways/OcelotApiGateway/Dockerfile
================================================
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["ApiGateways/OcelotApiGateway/OcelotApiGateway.csproj", "ApiGateways/OcelotApiGateway/"]
RUN dotnet restore "./ApiGateways/OcelotApiGateway/./OcelotApiGateway.csproj"
COPY . .
WORKDIR "/src/ApiGateways/OcelotApiGateway"
RUN dotnet build "./OcelotApiGateway.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./OcelotApiGateway.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "OcelotApiGateway.dll"]
================================================
FILE: ApiGateways/OcelotApiGateway/OcelotApiGateway.csproj
================================================
net8.0
enable
enable
Linux
..\..
..\..\docker-compose.dcproj
================================================
FILE: ApiGateways/OcelotApiGateway/Program.cs
================================================
using Ocelot.Cache.CacheManager;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Logging
.AddConfiguration(builder.Configuration.GetSection("Logging"))
.AddConsole()
.AddDebug();
builder.Configuration
.AddJsonFile($"ocelot.{builder.Environment.EnvironmentName}.json");
builder.Services
.AddOcelot()
.AddCacheManager(x =>
{
x.WithDictionaryHandle();
});
var app = builder.Build();
await app.UseOcelot();
app.Run();
================================================
FILE: ApiGateways/OcelotApiGateway/Properties/launchSettings.json
================================================
{
"profiles": {
"OcelotApiGateway": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Local"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5010"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Local"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8010"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:13294",
"sslPort": 0
}
}
}
================================================
FILE: ApiGateways/OcelotApiGateway/appsettings.Development.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
================================================
FILE: ApiGateways/OcelotApiGateway/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
================================================
FILE: ApiGateways/OcelotApiGateway/ocelot.Development.json
================================================
// BFF: Backend For Frontend
{
"Routes": [
// Catalog.Api
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "catalog.api",
"Port": 8000
}
],
"UpStreamPathTemplate": "/Catalog/{controller}/{action}",
"UpStreamHttpMethod": [ "GET", "POST", "PUT" ],
"FileCacheOptions": {
"TtlSeconds": 30
}
},
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}/{idOrNameOrCategory}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "catalog.api",
"Port": 8000
}
],
"UpStreamPathTemplate": "/Catalog/{controller}/{action}/{idOrNameOrCategory}",
"UpStreamHttpMethod": [ "GET", "DELETE" ]
},
// Basket.Api
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "basket.api",
"Port": 8001
}
],
"UpStreamPathTemplate": "/Basket/{controller}/{action}",
"UpStreamHttpMethod": [ "POST" ],
"RateLimitOptions": {
"ClientWhiteList": [],
"EnableRateLimiting": true,
"Period": "5s",
"PeriodTimeSpan": 1,
"Limit": 1
}
},
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}/{userName}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "basket.api",
"Port": 8001
}
],
"UpStreamPathTemplate": "/Basket/{controller}/{action}/{userName}",
"UpStreamHttpMethod": [ "GET", "DELETE" ]
},
// Discount.Api
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "discount.api",
"Port": 8002
}
],
"UpStreamPathTemplate": "/Discount/{controller}/{action}",
"UpStreamHttpMethod": [ "POST", "PUT" ]
},
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}/{productName}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "discount.api",
"Port": 8002
}
],
"UpStreamPathTemplate": "/Discount/{controller}/{action}/{productName}",
"UpStreamHttpMethod": [ "GET", "DELETE" ]
},
// Ordering.Api
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}/{userName}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "ordering.api",
"Port": 8004
}
],
"UpStreamPathTemplate": "/Ordering/{controller}/{action}/{userName}",
"UpStreamHttpMethod": [ "GET" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5010"
}
}
================================================
FILE: ApiGateways/OcelotApiGateway/ocelot.Local.json
================================================
// BFF: Backend For Frontend
{
"Routes": [
// Catalog.Api
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8000
}
],
"UpStreamPathTemplate": "/Catalog/{controller}/{action}",
"UpStreamHttpMethod": [ "GET", "POST", "PUT" ],
"FileCacheOptions": {
"TtlSeconds": 30
}
},
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}/{idOrNameOrCategory}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8000
}
],
"UpStreamPathTemplate": "/Catalog/{controller}/{action}/{idOrNameOrCategory}",
"UpStreamHttpMethod": [ "GET", "DELETE" ]
},
// Basket.Api
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8001
}
],
"UpStreamPathTemplate": "/Basket/{controller}/{action}",
"UpStreamHttpMethod": [ "POST" ],
"RateLimitOptions": {
"ClientWhiteList": [],
"EnableRateLimiting": true,
"Period": "5s",
"PeriodTimeSpan": 1,
"Limit": 1
}
},
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}/{userName}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8001
}
],
"UpStreamPathTemplate": "/Basket/{controller}/{action}/{userName}",
"UpStreamHttpMethod": [ "GET", "DELETE" ]
},
// Discount.Api
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8002
}
],
"UpStreamPathTemplate": "/Discount/{controller}/{action}",
"UpStreamHttpMethod": [ "POST", "PUT" ]
},
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}/{productName}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8002
}
],
"UpStreamPathTemplate": "/Discount/{controller}/{action}/{productName}",
"UpStreamHttpMethod": [ "GET", "DELETE" ]
},
// Ordering.Api
{
"DownStreamPathTemplate": "/api/v1/{controller}/{action}/{userName}",
"DownStreamScheme": "http",
"DownStreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8004
}
],
"UpStreamPathTemplate": "/Ordering/{controller}/{action}/{userName}",
"UpStreamHttpMethod": [ "GET" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5010"
}
}
================================================
FILE: ApiGateways/OcelotApiGateway/ocelot.json
================================================
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Controllers/ShoppingController.cs
================================================
using Microsoft.AspNetCore.Mvc;
using OnlineShop.Aggregator.DTOs;
using OnlineShop.Aggregator.Services;
using System.Net;
namespace OnlineShop.Aggregator.Controllers;
[ApiController]
[Route("api/v1/[controller]/[action]")]
public class ShoppingController(
ICatalogService _catalogService,
IBasketService _basketService,
IOrderService _orderService
) : ControllerBase
{
[HttpGet("{userName}")]
[ProducesResponseType(typeof(ShoppingDTO), (int)HttpStatusCode.OK)]
public async Task> GetByUserName(string userName)
{
var basket = await _basketService.GetBasket(userName);
foreach (var basketItem in basket.Items)
{
var product = await _catalogService.GetCatalog(basketItem.ProductId);
basketItem.ProductName = product.Name;
basketItem.Category = product.Category;
basketItem.Summary = product.Summary;
basketItem.Description = product.Description;
basketItem.ImageFile = product.ImageFile;
}
var orders = await _orderService.GetOrderByUserName(userName);
var shoppingDTO = new ShoppingDTO
{
UserName = userName,
BasketWithProduct = basket,
Orders = orders
};
return Ok(shoppingDTO);
}
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/DTOs/BasketDTO.cs
================================================
namespace OnlineShop.Aggregator.DTOs;
public class BasketDTO
{
public string UserName { get; set; } = string.Empty;
public List Items { get; set; } = [];
public decimal TotalPrice { get; set; }
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/DTOs/BasketItemExtendedDTO.cs
================================================
namespace OnlineShop.Aggregator.DTOs;
public class BasketItemExtendedDTO
{
public int Quantity { get; set; }
public string Color { get; set; } = string.Empty;
public decimal Price { get; set; }
public string ProductId { get; set; } = string.Empty;
public string ProductName { get; set; } = string.Empty;
// additional data
public string Category { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string ImageFile { get; set; } = string.Empty;
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/DTOs/CatalogDTO.cs
================================================
namespace OnlineShop.Aggregator.DTOs;
public class CatalogDTO
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string ImageFile { get; set; } = string.Empty;
public decimal Price { get; set; }
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/DTOs/OrderResponseDTO.cs
================================================
namespace OnlineShop.Aggregator.DTOs;
public class OrderResponseDTO
{
public string UserName { get; set; } = string.Empty;
public decimal TotalPrice { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string EmailAddress { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string BankName { get; set; } = string.Empty;
public string RefCode { get; set; } = string.Empty;
public int PaymentMethod { get; set; }
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/DTOs/ShoppingDTO.cs
================================================
namespace OnlineShop.Aggregator.DTOs;
public class ShoppingDTO
{
public string UserName { get; set; } = string.Empty;
public required BasketDTO BasketWithProduct { get; set; }
public IEnumerable Orders { get; set; } = [];
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Dockerfile
================================================
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/OnlineShop.Aggregator.csproj", "ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/"]
RUN dotnet restore "./ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/./OnlineShop.Aggregator.csproj"
COPY . .
WORKDIR "/src/ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator"
RUN dotnet build "./OnlineShop.Aggregator.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./OnlineShop.Aggregator.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "OnlineShop.Aggregator.dll"]
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Extensions/HttpClientExtensions.cs
================================================
using System.Text.Json;
namespace OnlineShop.Aggregator.Extensions;
public static class HttpClientExtensions
{
public static async Task ReadContentAs(this HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)
throw new ApplicationException($"Something went wrong when calling api, {response.ReasonPhrase}");
var dataAsString = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize(dataAsString)!;
}
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/OnlineShop.Aggregator.csproj
================================================
net8.0
enable
enable
true
Linux
..\..\..
..\..\..\docker-compose.dcproj
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/OnlineShop.Aggregator.http
================================================
@OnlineShop.Aggregator_HostAddress = http://localhost:5140
GET {{OnlineShop.Aggregator_HostAddress}}/weatherforecast/
Accept: application/json
###
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Program.cs
================================================
using OnlineShop.Aggregator.Services;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddOptions()
.Configure((options, serviceProvider) =>
{
options.PropertyNameCaseInsensitive = true;
});
builder.Services.AddHttpClient(c =>
{
c.BaseAddress = new Uri(builder.Configuration.GetValue("ApiSettings:CatalogUrl") ?? "");
});
builder.Services.AddHttpClient(c =>
{
c.BaseAddress = new Uri(builder.Configuration.GetValue("ApiSettings:BasketUrl") ?? "");
});
builder.Services.AddHttpClient(c =>
{
c.BaseAddress = new Uri(builder.Configuration.GetValue("ApiSettings:OrderingUrl") ?? "");
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapControllers();
app.Run();
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Properties/launchSettings.json
================================================
{
"profiles": {
"OnlineShop.Aggregator": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5005"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8005"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:49114",
"sslPort": 0
}
}
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Services/BasketService.cs
================================================
using OnlineShop.Aggregator.DTOs;
using OnlineShop.Aggregator.Extensions;
namespace OnlineShop.Aggregator.Services;
public class BasketService(HttpClient _client) : IBasketService
{
public async Task GetBasket(string userName)
{
var response = await _client.GetAsync($"/api/v1/Order/GetByUserName/{userName}");
return await response.ReadContentAs();
}
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Services/CatalogService.cs
================================================
using OnlineShop.Aggregator.DTOs;
using OnlineShop.Aggregator.Extensions;
namespace OnlineShop.Aggregator.Services;
public class CatalogService(HttpClient _client) : ICatalogService
{
public async Task> GetCatalog()
{
var response = await _client.GetAsync("/api/v1/Product/GetAll");
return await response.ReadContentAs>();
}
public async Task GetCatalog(string id)
{
var response = await _client.GetAsync($"/api/v1/Product/GetById/{id}");
return await response.ReadContentAs();
}
public async Task> GetCatalogByCategory(string category)
{
var response = await _client.GetAsync($"/api/v1/Product/GetByCategory/{category}");
return await response.ReadContentAs>();
}
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Services/IBasketService.cs
================================================
using OnlineShop.Aggregator.DTOs;
namespace OnlineShop.Aggregator.Services;
public interface IBasketService
{
Task GetBasket(string userName);
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Services/ICatalogService.cs
================================================
using OnlineShop.Aggregator.DTOs;
namespace OnlineShop.Aggregator.Services;
public interface ICatalogService
{
Task> GetCatalog();
Task GetCatalog(string id);
Task> GetCatalogByCategory(string category);
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Services/IOrderService.cs
================================================
using OnlineShop.Aggregator.DTOs;
namespace OnlineShop.Aggregator.Services;
public interface IOrderService
{
Task> GetOrderByUserName(string userName);
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/Services/OrderService.cs
================================================
using OnlineShop.Aggregator.DTOs;
using OnlineShop.Aggregator.Extensions;
namespace OnlineShop.Aggregator.Services;
public class OrderService(HttpClient _client) : IOrderService
{
public async Task> GetOrderByUserName(string userName)
{
var response = await _client.GetAsync($"/api/v1/Order/GetOrdersByUserName/{userName}");
return await response.ReadContentAs>();
}
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/appsettings.Development.json
================================================
{
"ApiSettings": {
"CatalogUrl": "http://localhost:8000",
"BasketUrl": "http://localhost:8001",
"OrderingUrl": "http://localhost:8004"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
================================================
FILE: ApiGateways/OnlineShop.Aggregator/OnlineShop.Aggregator/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
================================================
FILE: BuildingBlocks/EventBus.Messages/Common/EventBusConstant.cs
================================================
namespace EventBus.Messages.Common;
public class EventBusConstant
{
public const string BasketCheckoutQueue = "BasketCheckout-queue";
}
================================================
FILE: BuildingBlocks/EventBus.Messages/EventBus.Messages.csproj
================================================
net8.0
enable
enable
================================================
FILE: BuildingBlocks/EventBus.Messages/Events/BasketCheckoutEvent.cs
================================================
namespace EventBus.Messages.Events;
public class BasketCheckoutEvent : IntegrationBaseEvent
{
public string UserName { get; set; } = string.Empty;
public decimal TotalPrice { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string EmailAddress { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string BankName { get; set; } = string.Empty;
public string RefCode { get; set; } = string.Empty;
public int PaymentMethod { get; set; }
}
================================================
FILE: BuildingBlocks/EventBus.Messages/Events/IntegrationBaseEvent.cs
================================================
namespace EventBus.Messages.Events;
public class IntegrationBaseEvent
{
public IntegrationBaseEvent()
{
Id = Guid.NewGuid();
CreatedAt = DateTime.Now;
}
public IntegrationBaseEvent(Guid id, DateTime createdAt)
{
Id = id;
CreatedAt = createdAt;
}
public Guid Id { get; private set; }
public DateTime CreatedAt { get; private set; }
}
================================================
FILE: OnlineShop.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34221.43
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{087760BA-AF35-40D8-8779-01B3C48D3EF4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Catalog", "Catalog", "{653AE14C-8DD7-4E9C-96B5-D6B99C1D0CF7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Catalog.Api", "Services\Catalog\Catalog.Api\Catalog.Api.csproj", "{A6F2D049-4B6E-4805-889F-49D113927DA6}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{9ED1D254-4DE6-4229-B3B8-A60383414787}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Basket", "Basket", "{9830C312-7C6A-4AAE-9700-AF8E2BCF942D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Basket.Api", "Services\Basket\Basket.Api\Basket.Api.csproj", "{820840D7-0105-4611-B956-235D6EE15191}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Discount", "Discount", "{4166A6E7-178A-4FDE-B7BD-33C5833F9472}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discount.Api", "Services\Discount\Discount.Api\Discount.Api.csproj", "{C8BD72CC-5DDA-4A81-8A5B-FB411D1707ED}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discount.Grpc", "Services\Discount\Discount.Grpc\Discount.Grpc.csproj", "{DAD4D4A1-9AEB-46CF-A143-D19C1EB9C36D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Ordering", "Ordering", "{69172560-B5A3-488A-A715-2754C6FA6C5D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ordering.Api", "Services\Ordering\Ordering.Api\Ordering.Api.csproj", "{F26D75D9-B050-4F78-8911-65780DF78BA5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ordering.Domain", "Services\Ordering\Ordering.Domain\Ordering.Domain.csproj", "{334B2EC6-F84D-47F2-83C3-7F4FECAA1163}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ordering.Application", "Services\Ordering\Ordering.Application\Ordering.Application.csproj", "{5547B022-4512-4765-A762-121A77F9D204}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ordering.Infrastructure", "Services\Ordering\Ordering.Infrastructure\Ordering.Infrastructure.csproj", "{884BB166-77C2-4B71-9E97-9739616120B4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{697BC681-3FB0-437B-9BB9-AF5BA4C929E0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Messages", "BuildingBlocks\EventBus.Messages\EventBus.Messages.csproj", "{2318E480-B8A6-40F4-84E4-55A8162A4969}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiGateways", "ApiGateways", "{9B4D3E8A-2363-4DB9-8B9A-CC7803EE3F86}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotApiGateway", "ApiGateways\OcelotApiGateway\OcelotApiGateway.csproj", "{14C156A3-CDE8-4501-A61A-1FF511F6255E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnlineShop.Aggregator", "ApiGateways\OnlineShop.Aggregator\OnlineShop.Aggregator\OnlineShop.Aggregator.csproj", "{3C7D0E5D-90CE-469B-AC79-1B89EEEFE3B8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A6F2D049-4B6E-4805-889F-49D113927DA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6F2D049-4B6E-4805-889F-49D113927DA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6F2D049-4B6E-4805-889F-49D113927DA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6F2D049-4B6E-4805-889F-49D113927DA6}.Release|Any CPU.Build.0 = Release|Any CPU
{9ED1D254-4DE6-4229-B3B8-A60383414787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9ED1D254-4DE6-4229-B3B8-A60383414787}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9ED1D254-4DE6-4229-B3B8-A60383414787}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9ED1D254-4DE6-4229-B3B8-A60383414787}.Release|Any CPU.Build.0 = Release|Any CPU
{820840D7-0105-4611-B956-235D6EE15191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{820840D7-0105-4611-B956-235D6EE15191}.Debug|Any CPU.Build.0 = Debug|Any CPU
{820840D7-0105-4611-B956-235D6EE15191}.Release|Any CPU.ActiveCfg = Release|Any CPU
{820840D7-0105-4611-B956-235D6EE15191}.Release|Any CPU.Build.0 = Release|Any CPU
{C8BD72CC-5DDA-4A81-8A5B-FB411D1707ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8BD72CC-5DDA-4A81-8A5B-FB411D1707ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8BD72CC-5DDA-4A81-8A5B-FB411D1707ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8BD72CC-5DDA-4A81-8A5B-FB411D1707ED}.Release|Any CPU.Build.0 = Release|Any CPU
{DAD4D4A1-9AEB-46CF-A143-D19C1EB9C36D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DAD4D4A1-9AEB-46CF-A143-D19C1EB9C36D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAD4D4A1-9AEB-46CF-A143-D19C1EB9C36D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAD4D4A1-9AEB-46CF-A143-D19C1EB9C36D}.Release|Any CPU.Build.0 = Release|Any CPU
{F26D75D9-B050-4F78-8911-65780DF78BA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F26D75D9-B050-4F78-8911-65780DF78BA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F26D75D9-B050-4F78-8911-65780DF78BA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F26D75D9-B050-4F78-8911-65780DF78BA5}.Release|Any CPU.Build.0 = Release|Any CPU
{334B2EC6-F84D-47F2-83C3-7F4FECAA1163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{334B2EC6-F84D-47F2-83C3-7F4FECAA1163}.Debug|Any CPU.Build.0 = Debug|Any CPU
{334B2EC6-F84D-47F2-83C3-7F4FECAA1163}.Release|Any CPU.ActiveCfg = Release|Any CPU
{334B2EC6-F84D-47F2-83C3-7F4FECAA1163}.Release|Any CPU.Build.0 = Release|Any CPU
{5547B022-4512-4765-A762-121A77F9D204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5547B022-4512-4765-A762-121A77F9D204}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5547B022-4512-4765-A762-121A77F9D204}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5547B022-4512-4765-A762-121A77F9D204}.Release|Any CPU.Build.0 = Release|Any CPU
{884BB166-77C2-4B71-9E97-9739616120B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{884BB166-77C2-4B71-9E97-9739616120B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{884BB166-77C2-4B71-9E97-9739616120B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{884BB166-77C2-4B71-9E97-9739616120B4}.Release|Any CPU.Build.0 = Release|Any CPU
{2318E480-B8A6-40F4-84E4-55A8162A4969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2318E480-B8A6-40F4-84E4-55A8162A4969}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2318E480-B8A6-40F4-84E4-55A8162A4969}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2318E480-B8A6-40F4-84E4-55A8162A4969}.Release|Any CPU.Build.0 = Release|Any CPU
{14C156A3-CDE8-4501-A61A-1FF511F6255E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14C156A3-CDE8-4501-A61A-1FF511F6255E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14C156A3-CDE8-4501-A61A-1FF511F6255E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14C156A3-CDE8-4501-A61A-1FF511F6255E}.Release|Any CPU.Build.0 = Release|Any CPU
{3C7D0E5D-90CE-469B-AC79-1B89EEEFE3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3C7D0E5D-90CE-469B-AC79-1B89EEEFE3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C7D0E5D-90CE-469B-AC79-1B89EEEFE3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C7D0E5D-90CE-469B-AC79-1B89EEEFE3B8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{653AE14C-8DD7-4E9C-96B5-D6B99C1D0CF7} = {087760BA-AF35-40D8-8779-01B3C48D3EF4}
{A6F2D049-4B6E-4805-889F-49D113927DA6} = {653AE14C-8DD7-4E9C-96B5-D6B99C1D0CF7}
{9830C312-7C6A-4AAE-9700-AF8E2BCF942D} = {087760BA-AF35-40D8-8779-01B3C48D3EF4}
{820840D7-0105-4611-B956-235D6EE15191} = {9830C312-7C6A-4AAE-9700-AF8E2BCF942D}
{4166A6E7-178A-4FDE-B7BD-33C5833F9472} = {087760BA-AF35-40D8-8779-01B3C48D3EF4}
{C8BD72CC-5DDA-4A81-8A5B-FB411D1707ED} = {4166A6E7-178A-4FDE-B7BD-33C5833F9472}
{DAD4D4A1-9AEB-46CF-A143-D19C1EB9C36D} = {4166A6E7-178A-4FDE-B7BD-33C5833F9472}
{69172560-B5A3-488A-A715-2754C6FA6C5D} = {087760BA-AF35-40D8-8779-01B3C48D3EF4}
{F26D75D9-B050-4F78-8911-65780DF78BA5} = {69172560-B5A3-488A-A715-2754C6FA6C5D}
{334B2EC6-F84D-47F2-83C3-7F4FECAA1163} = {69172560-B5A3-488A-A715-2754C6FA6C5D}
{5547B022-4512-4765-A762-121A77F9D204} = {69172560-B5A3-488A-A715-2754C6FA6C5D}
{884BB166-77C2-4B71-9E97-9739616120B4} = {69172560-B5A3-488A-A715-2754C6FA6C5D}
{2318E480-B8A6-40F4-84E4-55A8162A4969} = {697BC681-3FB0-437B-9BB9-AF5BA4C929E0}
{14C156A3-CDE8-4501-A61A-1FF511F6255E} = {9B4D3E8A-2363-4DB9-8B9A-CC7803EE3F86}
{3C7D0E5D-90CE-469B-AC79-1B89EEEFE3B8} = {9B4D3E8A-2363-4DB9-8B9A-CC7803EE3F86}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A359A298-1C98-4D56-886A-777076E176E4}
EndGlobalSection
EndGlobal
================================================
FILE: README.md
================================================
# OnlineShop
🚀 **OnlineShop** is a comprehensive and feature-rich template repository for building robust .NET 8 applications.
---
## ⭐ Star This Repository!
---
## 🌟 What’s Inside OnlineShop?
This repository is packed with:
### 🌐 Services
- ➡ **Basket**
- ➡ **Catalog**
- ➡ **Discount**
- ➡ **Ordering**
### 🛑 APIGateways & BuildingBlocks
- ➡ **Clean Architecture** for scalable applications
- ➡ **CQRS pattern** for separating read and write operations
- ➡ **Unit of Work** & **Repository patterns**
- ➡ **EF Core** & **Dapper** for data access
### 🗄️ Database & Caching Support
- ➡ **SQLServer**, **Postgres**, & **MongoDB**
- ➡ **Redis** for high-performance caching
### 🛠 Middleware & Error Handling
- ➡ **BaseResult pattern** for uniform API responses
- ➡ **RabbitMQ** for messaging and background jobs
### 📊 Load Balancing & Aggregator
- ➡ **YARP** for Load Balancing
- ➡ **Ocelot** for API Gateway
### 🚀 API & Authentication
- ➡ **JWT tokens** & **OAuth** for secure authentication and authorization
### 🐳 Docker & DevOps
- ➡ **Docker** support for containerization
- ➡ **pgAdmin** for database management
- ➡ **Portainer** for easy Docker management
### 📋 Swagger & API Management
- ➡ Fully configured **Swagger** with security and examples
### 📌 Additional Tools & Patterns
- ➡ **Custom Exceptions** and **Pagination Handlers**
- ➡ Best practices in **DDD** and **OOP**
---
## 🔗 Explore the Repository
You can find all these features and more in the **OnlineShop** repository on GitHub. Feel free to **explore**, **fork**, and **contribute**!
👉 [OnlineShop on GitHub](https://lnkd.in/d9aruGDU)
---
## 🤝 Get Involved!
Contributions, feedback, and suggestions are highly welcome! Let’s collaborate to make **OnlineShop** even better.
---
## Stay Connected
- **GitHub**: [BehzadDara](https://github.com/BehzadDara)
- **LinkedIn**: [Behzad Dara](https://www.linkedin.com/in/behzaddara/)
================================================
FILE: Services/Basket/Basket.Api/Basket.Api.csproj
================================================
net8.0
enable
enable
true
Linux
..\..\..
..\..\..\docker-compose.dcproj
Protos\discount.proto
================================================
FILE: Services/Basket/Basket.Api/Basket.Api.http
================================================
@Basket.Api_HostAddress = http://localhost:5033
GET {{Basket.Api_HostAddress}}/weatherforecast/
Accept: application/json
###
================================================
FILE: Services/Basket/Basket.Api/Controllers/OrderController.cs
================================================
using AutoMapper;
using Basket.Api.Entities;
using Basket.Api.GrpcServices;
using Basket.Api.Repositories;
using EventBus.Messages.Events;
using MassTransit;
using MassTransit.Transports;
using Microsoft.AspNetCore.Mvc;
using System.Net;
namespace Basket.Api.Controllers
{
[Route("api/v1/[controller]/[action]")]
[ApiController]
public class OrderController(
IOrderRepository _orderRepository,
DiscountGrpcService _discountService,
IMapper _mapper,
IPublishEndpoint _publishEndpoint
) : ControllerBase
{
[HttpGet("{userName}")]
[ProducesResponseType(typeof(Order), (int)HttpStatusCode.OK)]
public async Task> GetByUserName(string userName)
{
var result = await _orderRepository.GetByUserName(userName);
return Ok(result);
}
[HttpPost]
[ProducesResponseType(typeof(Order), (int)HttpStatusCode.OK)]
public async Task> Update([FromBody] Order order)
{
foreach (var orderItem in order.Items)
{
var coupon = await _discountService.GetDiscount(orderItem.ProductName);
orderItem.Price -= coupon.Amount;
}
var result = await _orderRepository.Update(order);
return Ok(result);
}
[HttpDelete("{userName}")]
[ProducesResponseType(typeof(void), (int)HttpStatusCode.OK)]
public async Task Delete(string userName)
{
await _orderRepository.Delete(userName);
return Ok();
}
[HttpPost]
[ProducesResponseType((int)HttpStatusCode.Accepted)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task Checkout([FromBody] BasketCheckout basketCheckout)
{
var basket = await _orderRepository.GetByUserName(basketCheckout.UserName);
if (basket is null)
{
return BadRequest();
}
var eventMessage = _mapper.Map(basketCheckout);
eventMessage.TotalPrice = basket.TotalPrice;
await _publishEndpoint.Publish(eventMessage);
await _orderRepository.Delete(basketCheckout.UserName);
return Accepted();
}
}
}
================================================
FILE: Services/Basket/Basket.Api/Dockerfile
================================================
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Services/Basket/Basket.Api/Basket.Api.csproj", "Services/Basket/Basket.Api/"]
COPY ["BuildingBlocks/EventBus.Messages/EventBus.Messages.csproj", "BuildingBlocks/EventBus.Messages/"]
RUN dotnet restore "./Services/Basket/Basket.Api/./Basket.Api.csproj"
COPY . .
WORKDIR "/src/Services/Basket/Basket.Api"
RUN dotnet build "./Basket.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Basket.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Basket.Api.dll"]
================================================
FILE: Services/Basket/Basket.Api/Entities/BasketCheckout.cs
================================================
namespace Basket.Api.Entities;
public class BasketCheckout
{
public string UserName { get; set; } = string.Empty;
public decimal TotalPrice { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string EmailAddress { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string BankName { get; set; } = string.Empty;
public string RefCode { get; set; } = string.Empty;
public int PaymentMethod { get; set; }
}
================================================
FILE: Services/Basket/Basket.Api/Entities/Order.cs
================================================
namespace Basket.Api.Entities;
public class Order(string userName)
{
public string UserName { get; set; } = userName;
public List Items { get; set; } = [];
public decimal TotalPrice
{
get
{
return Items.Sum(x => x.Quantity * x.Price);
}
}
}
================================================
FILE: Services/Basket/Basket.Api/Entities/OrderItem.cs
================================================
namespace Basket.Api.Entities;
public class OrderItem
{
public int Quantity { get; set; }
public string Color { get; set; } = string.Empty;
public decimal Price { get; set; }
public string ProductId { get; set; } = string.Empty;
public string ProductName { get; set; } = string.Empty;
}
================================================
FILE: Services/Basket/Basket.Api/GrpcServices/DiscountGrpcService.cs
================================================
using Discount.Grpc.Protos;
namespace Basket.Api.GrpcServices;
public class DiscountGrpcService(DiscountProtoService.DiscountProtoServiceClient _discountProtoService)
{
public async Task GetDiscount(string ProductName)
{
var discountRequest = new GetDiscountRequest { ProductName = ProductName };
return await _discountProtoService.GetDiscountAsync(discountRequest);
}
}
================================================
FILE: Services/Basket/Basket.Api/Mapper/BasketProfile.cs
================================================
using AutoMapper;
using Basket.Api.Entities;
using EventBus.Messages.Events;
namespace Basket.Api.Mapper;
public class BasketProfile : Profile
{
public BasketProfile()
{
CreateMap().ReverseMap();
}
}
================================================
FILE: Services/Basket/Basket.Api/Program.cs
================================================
using Basket.Api.GrpcServices;
using Basket.Api.Repositories;
using Discount.Grpc.Protos;
using MassTransit;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetValue("CacheSettings:ConnectionString");
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddGrpcClient(options =>
{
options.Address = new Uri(builder.Configuration.GetValue("GrpcSettings:DiscountUrl") ?? "");
});
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddMassTransit(config =>
{
config.UsingRabbitMq((context, conf) =>
{
conf.Host(builder.Configuration.GetValue("EventBusSettings:HostAddress"));
});
});
builder.Services.AddAutoMapper(typeof(Program));
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapControllers();
app.Run();
================================================
FILE: Services/Basket/Basket.Api/Properties/launchSettings.json
================================================
{
"profiles": {
"Basket.Api": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5001"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8001"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:19262",
"sslPort": 0
}
}
}
================================================
FILE: Services/Basket/Basket.Api/Repositories/IOrderRepository.cs
================================================
using Basket.Api.Entities;
namespace Basket.Api.Repositories;
public interface IOrderRepository
{
Task GetByUserName(string userName);
Task Update(Order order);
Task Delete(string userName);
}
================================================
FILE: Services/Basket/Basket.Api/Repositories/OrderRepository.cs
================================================
using Basket.Api.Entities;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
namespace Basket.Api.Repositories;
public class OrderRepository(IDistributedCache _redisCache) : IOrderRepository
{
public async Task GetByUserName(string userName)
{
var result = await _redisCache.GetStringAsync(userName);
if (string.IsNullOrEmpty(result))
{
return new Order(userName);
}
var order = JsonConvert.DeserializeObject(result);
if (order is null)
{
return new Order(userName);
}
return order;
}
public async Task Update(Order order)
{
await _redisCache.SetStringAsync(order.UserName, JsonConvert.SerializeObject(order));
return await GetByUserName(order.UserName);
}
public async Task Delete(string userName)
{
await _redisCache.RemoveAsync(userName);
}
}
================================================
FILE: Services/Basket/Basket.Api/appsettings.Development.json
================================================
{
"CacheSettings": {
"ConnectionString": "localhost:6379"
},
"GrpcSettings": {
"DiscountUrl": "http://localhost:5003"
},
"EventBusSettings": {
"HostAddress": "amqp://guest:guest@localhost:5672"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
================================================
FILE: Services/Basket/Basket.Api/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
================================================
FILE: Services/Catalog/Catalog.Api/Catalog.Api.csproj
================================================
net8.0
enable
enable
true
Linux
..\..\..
..\..\..\docker-compose.dcproj
================================================
FILE: Services/Catalog/Catalog.Api/Catalog.Api.http
================================================
@Catalog.Api_HostAddress = http://localhost:5049
GET {{Catalog.Api_HostAddress}}/weatherforecast/
Accept: application/json
###
================================================
FILE: Services/Catalog/Catalog.Api/Controllers/ProductController.cs
================================================
using Catalog.Api.Entities;
using Catalog.Api.Repositories;
using Microsoft.AspNetCore.Mvc;
using System.Net;
namespace Catalog.Api.Controllers
{
[Route("api/v1/[controller]/[action]")]
[ApiController]
public class ProductController(IProductRepository _productRepository, ILogger _logger) : ControllerBase
{
[HttpGet]
[ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)]
public async Task>> GetAll()
{
var result = await _productRepository.GetAll();
return Ok(result);
}
[HttpGet("{id:length(24)}")]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task> GetById(string id)
{
var result = await _productRepository.GetById(id);
if (result is null)
{
_logger.LogError($"product with id: {id} not found.");
return NotFound();
}
return Ok(result);
}
[HttpGet("{name}")]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task> GetByName(string name)
{
var result = await _productRepository.GetByName(name);
if (result is null)
{
_logger.LogError($"product with name: {name} not found.");
return NotFound();
}
return Ok(result);
}
[HttpGet("{category}")]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task>> GetByCategory(string category)
{
var result = await _productRepository.GetByCategory(category);
return Ok(result);
}
[HttpPost]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task>> Create([FromBody] Product product)
{
await _productRepository.Create(product);
return CreatedAtRoute("GetProduct", new { id = product.Id}, product);
}
[HttpPut]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task>> Update([FromBody] Product product)
{
var result = await _productRepository.Update(product);
return Ok(result);
}
[HttpDelete("{id:length(24)}")]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task>> Delete(string id)
{
var result = await _productRepository.Delete(id);
return Ok(result);
}
}
}
================================================
FILE: Services/Catalog/Catalog.Api/Data/CatalogContext.cs
================================================
using Catalog.Api.Entities;
using MongoDB.Driver;
namespace Catalog.Api.Data;
public class CatalogContext : ICatalogContext
{
public CatalogContext(IConfiguration configuration)
{
var client = new MongoClient(configuration.GetValue("DatabaseSettings:ConnectionString"));
var database = client.GetDatabase(configuration.GetValue("DatabaseSettings:DatabaseName"));
Products = database.GetCollection(configuration.GetValue("DatabaseSettings:CollectionName"));
CatalogContextSeed.SeedData(Products);
}
public IMongoCollection Products { get; }
}
================================================
FILE: Services/Catalog/Catalog.Api/Data/CatalogContextSeed.cs
================================================
using Catalog.Api.Entities;
using MongoDB.Driver;
namespace Catalog.Api.Data;
public class CatalogContextSeed
{
public static void SeedData(IMongoCollection productCollection)
{
var existProduct = productCollection.Find(x => true).Any();
if (!existProduct)
{
productCollection.InsertManyAsync(GetSeedData());
}
}
private static List GetSeedData()
{
return
[
new()
{
Id = "602d2149e773f2a3990b47f5",
Name = "IPhone X",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus.",
ImageFile = "product-1.png",
Price = 950.00M,
Category = "Smart Phone"
},
new()
{
Id = "602d2149e773f2a3990b47f6",
Name = "Samsung 10",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus.",
ImageFile = "product-2.png",
Price = 840.00M,
Category = "Smart Phone"
},
new()
{
Id = "602d2149e773f2a3990b47f7",
Name = "Huawei Plus",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus.",
ImageFile = "product-3.png",
Price = 650.00M,
Category = "White Appliances"
},
new()
{
Id = "602d2149e773f2a3990b47f8",
Name = "Xiaomi Mi 9",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus.",
ImageFile = "product-4.png",
Price = 470.00M,
Category = "White Appliances"
},
new()
{
Id = "602d2149e773f2a3990b47f9",
Name = "HTC U11+ Plus",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus.",
ImageFile = "product-5.png",
Price = 380.00M,
Category = "Smart Phone"
},
new()
{
Id = "602d2149e773f2a3990b47fa",
Name = "LG G7 ThinQ",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus.",
ImageFile = "product-6.png",
Price = 240.00M,
Category = "Home Kitchen"
}
];
}
}
================================================
FILE: Services/Catalog/Catalog.Api/Data/ICatalogContext.cs
================================================
using Catalog.Api.Entities;
using MongoDB.Driver;
namespace Catalog.Api.Data;
public interface ICatalogContext
{
IMongoCollection Products { get; }
}
================================================
FILE: Services/Catalog/Catalog.Api/Dockerfile
================================================
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Services/Catalog/Catalog.Api/Catalog.Api.csproj", "Services/Catalog/Catalog.Api/"]
RUN dotnet restore "./Services/Catalog/Catalog.Api/./Catalog.Api.csproj"
COPY . .
WORKDIR "/src/Services/Catalog/Catalog.Api"
RUN dotnet build "./Catalog.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Catalog.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Catalog.Api.dll"]
================================================
FILE: Services/Catalog/Catalog.Api/Entities/Product.cs
================================================
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Catalog.Api.Entities;
public class Product
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; } = string.Empty;
[BsonElement("Name")]
public string Name { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string ImageFile { get; set; } = string.Empty;
public decimal Price { get; set; }
}
================================================
FILE: Services/Catalog/Catalog.Api/Products.Json
================================================
[
{
"Name": "Asus Laptop",
"Category": "Computers",
"Summary": "Summary",
"Description": "Description",
"ImageFile": "ImageFile",
"Price": "100"
},
{
"Name": "HP Laptop",
"Category": "Computers",
"Summary": "Summary",
"Description": "Description",
"ImageFile": "ImageFile",
"Price": "200"
}
]
================================================
FILE: Services/Catalog/Catalog.Api/Program.cs
================================================
using Catalog.Api.Data;
using Catalog.Api.Repositories;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped();
builder.Services.AddScoped();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapControllers();
app.Run();
================================================
FILE: Services/Catalog/Catalog.Api/Properties/launchSettings.json
================================================
{
"profiles": {
"Catalog.Api": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5000"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8000"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57789",
"sslPort": 0
}
}
}
================================================
FILE: Services/Catalog/Catalog.Api/Repositories/IProductRepository.cs
================================================
using Catalog.Api.Entities;
namespace Catalog.Api.Repositories;
public interface IProductRepository
{
Task> GetAll();
Task GetById(string id);
Task GetByName(string name);
Task> GetByCategory(string category);
Task Create(Product product);
Task Update(Product product);
Task Delete(string id);
}
================================================
FILE: Services/Catalog/Catalog.Api/Repositories/ProductRepository.cs
================================================
using Catalog.Api.Data;
using Catalog.Api.Entities;
using MongoDB.Driver;
namespace Catalog.Api.Repositories;
public class ProductRepository(ICatalogContext _catalogContext) : IProductRepository
{
public async Task> GetAll()
{
return await _catalogContext.Products.Find(x => true).ToListAsync();
}
public async Task GetById(string id)
{
return await _catalogContext.Products.Find(x => x.Id == id).FirstOrDefaultAsync();
}
public async Task GetByName(string name)
{
return await _catalogContext.Products.Find(x => x.Name == name).FirstOrDefaultAsync();
}
public async Task> GetByCategory(string category)
{
return await _catalogContext.Products.Find(x => x.Category == category).ToListAsync();
}
public async Task Create(Product product)
{
await _catalogContext.Products.InsertOneAsync(product);
}
public async Task Update(Product product)
{
var result = await _catalogContext.Products.ReplaceOneAsync(x => x.Id == product.Id, product);
return result.IsAcknowledged && result.ModifiedCount > 0;
}
public async Task Delete(string id)
{
var result = await _catalogContext.Products.DeleteOneAsync(x => x.Id == id);
return result.IsAcknowledged && result.DeletedCount > 0;
}
}
================================================
FILE: Services/Catalog/Catalog.Api/appsettings.Development.json
================================================
{
"DatabaseSettings": {
"ConnectionString": "localhost://catalogdb:27017",
"DatabaseName": "CatalogDB",
"CollectionName": "Products"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
================================================
FILE: Services/Catalog/Catalog.Api/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
================================================
FILE: Services/Discount/Discount.Api/Controllers/CouponController.cs
================================================
using Discount.Api.Entities;
using Discount.Api.Repositories;
using Microsoft.AspNetCore.Mvc;
using System.Net;
namespace Discount.Api.Controllers
{
[Route("api/v1/[controller]/[action]")]
[ApiController]
public class CouponController(ICouponRepository _couponRepository) : ControllerBase
{
[HttpGet("{productName}")]
[ProducesResponseType(typeof(Coupon), (int)HttpStatusCode.OK)]
public async Task> GetByProductName(string productName)
{
var result = await _couponRepository.GetByProductName(productName);
return Ok(result);
}
[HttpPost]
[ProducesResponseType(typeof(Coupon), (int)HttpStatusCode.OK)]
public async Task> Create([FromBody] Coupon coupon)
{
await _couponRepository.Create(coupon);
return await GetByProductName(coupon.ProductName);
}
[HttpPut]
[ProducesResponseType(typeof(Coupon), (int)HttpStatusCode.OK)]
public async Task> Update([FromBody] Coupon coupon)
{
await _couponRepository.Update(coupon);
return await GetByProductName(coupon.ProductName);
}
[HttpDelete("{productName}")]
[ProducesResponseType(typeof(bool), (int)HttpStatusCode.OK)]
public async Task> Delete(string productName)
{
var result = await _couponRepository.Delete(productName);
return Ok(result);
}
}
}
================================================
FILE: Services/Discount/Discount.Api/Discount.Api.csproj
================================================
net8.0
enable
enable
true
Linux
..\..\..
..\..\..\docker-compose.dcproj
================================================
FILE: Services/Discount/Discount.Api/Discount.Api.http
================================================
@Discount.Api_HostAddress = http://localhost:5297
GET {{Discount.Api_HostAddress}}/weatherforecast/
Accept: application/json
###
================================================
FILE: Services/Discount/Discount.Api/Dockerfile
================================================
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Services/Discount/Discount.Api/Discount.Api.csproj", "Services/Discount/Discount.Api/"]
RUN dotnet restore "./Services/Discount/Discount.Api/./Discount.Api.csproj"
COPY . .
WORKDIR "/src/Services/Discount/Discount.Api"
RUN dotnet build "./Discount.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Discount.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Discount.Api.dll"]
================================================
FILE: Services/Discount/Discount.Api/Entities/Coupon.cs
================================================
namespace Discount.Api.Entities;
public class Coupon
{
public int Id { get; set; }
public string ProductName { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int Amount { get; set; }
}
================================================
FILE: Services/Discount/Discount.Api/Extensions/HostExtensions.cs
================================================
using Npgsql;
namespace Discount.Api.Extensions;
public static class HostExtensions
{
public static IHost MigrateDatabase(this IHost host, int retry = 0)
{
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
var configuration = services.GetRequiredService();
var logger = services.GetRequiredService>();
try
{
logger.LogInformation("migrating postgresql database");
using var connection = new NpgsqlConnection
(configuration.GetValue("DatabaseSettings:ConnectionString"));
connection.Open();
using var command = new NpgsqlCommand
{
Connection = connection
};
command.CommandText = @"create table if not exists Coupon
(Id serial primary key,
ProductName varchar(200) not null,
Description text,
Amount int)";
command.ExecuteNonQuery();
/*command.CommandText = @"insert into Coupon(ProductName, Description, Amount) values
('IPhone X', 'iphone discount', 150),
('Samsung 10', 'samsung discount', 150)";
command.ExecuteNonQuery();*/
logger.LogInformation("migration has been completed!");
}
catch (Exception ex)
{
logger.LogError($"an error has been occured: {ex.Message}");
if (retry < 50)
{
Thread.Sleep(2000);
host.MigrateDatabase(retry + 1);
}
}
return host;
}
}
================================================
FILE: Services/Discount/Discount.Api/Program.cs
================================================
using Discount.Api.Extensions;
using Discount.Api.Repositories;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapControllers();
app.MigrateDatabase();
app.Run();
================================================
FILE: Services/Discount/Discount.Api/Properties/launchSettings.json
================================================
{
"profiles": {
"Discount.Api": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5002"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:18137",
"sslPort": 0
}
}
}
================================================
FILE: Services/Discount/Discount.Api/Repositories/CouponRepository.cs
================================================
using Dapper;
using Discount.Api.Entities;
using Npgsql;
namespace Discount.Api.Repositories;
public class CouponRepository(IConfiguration _configuration) : ICouponRepository
{
public async Task GetByProductName(string productName)
{
using var connection = new NpgsqlConnection
(_configuration.GetValue("DatabaseSettings:ConnectionString"));
var coupon = await connection.QueryFirstOrDefaultAsync
($"select * from Coupon where ProductName = '{productName}'");
if (coupon is null)
{
return new Coupon
{
ProductName = productName,
Description = "No discount",
Amount = 0
};
}
return coupon;
}
public async Task Create(Coupon coupon)
{
using var connection = new NpgsqlConnection
(_configuration.GetValue("DatabaseSettings:ConnectionString"));
var affected = await connection.ExecuteAsync
($"insert into Coupon(ProductName, Description, Amount) " +
$"values('{coupon.ProductName}', '{coupon.Description}', {coupon.Amount})");
return affected > 0;
}
public async Task Update(Coupon coupon)
{
using var connection = new NpgsqlConnection
(_configuration.GetValue("DatabaseSettings:ConnectionString"));
var affected = await connection.ExecuteAsync
($"update Coupon " +
$"set ProductName = '{coupon.ProductName}', Description = '{coupon.Description}', Amount = {coupon.Amount} " +
$"where Id = {coupon.Id}");
return affected > 0;
}
public async Task Delete(string productName)
{
using var connection = new NpgsqlConnection
(_configuration.GetValue("DatabaseSettings:ConnectionString"));
var affected = await connection.ExecuteAsync
($"delete from Coupon where ProductName = {productName}");
return affected > 0;
}
}
================================================
FILE: Services/Discount/Discount.Api/Repositories/ICouponRepository.cs
================================================
using Discount.Api.Entities;
namespace Discount.Api.Repositories;
public interface ICouponRepository
{
Task GetByProductName(string productName);
Task Create(Coupon coupon);
Task Update(Coupon coupon);
Task Delete(string productName);
}
================================================
FILE: Services/Discount/Discount.Api/appsettings.Development.json
================================================
{
"DatabaseSettings": {
"ConnectionString": "Server=localhost;port=5432;Database=discountdb;User Id=admin;Password=admin1234;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
================================================
FILE: Services/Discount/Discount.Api/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
================================================
FILE: Services/Discount/Discount.Grpc/Discount.Grpc.csproj
================================================
net8.0
enable
enable
Linux
..\..\..
..\..\..\docker-compose.dcproj
================================================
FILE: Services/Discount/Discount.Grpc/Dockerfile
================================================
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Services/Discount/Discount.Grpc/Discount.Grpc.csproj", "Services/Discount/Discount.Grpc/"]
RUN dotnet restore "./Services/Discount/Discount.Grpc/./Discount.Grpc.csproj"
COPY . .
WORKDIR "/src/Services/Discount/Discount.Grpc"
RUN dotnet build "./Discount.Grpc.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Discount.Grpc.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Discount.Grpc.dll"]
================================================
FILE: Services/Discount/Discount.Grpc/Entities/Coupon.cs
================================================
namespace Discount.Grpc.Entities;
public class Coupon
{
public int Id { get; set; }
public string ProductName { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int Amount { get; set; }
}
================================================
FILE: Services/Discount/Discount.Grpc/Extensions/HostExtensions.cs
================================================
using Npgsql;
namespace Discount.Grpc.Extensions;
public static class HostExtensions
{
public static IHost MigrateDatabase(this IHost host, int retry = 0)
{
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
var configuration = services.GetRequiredService();
var logger = services.GetRequiredService>();
try
{
logger.LogInformation("migrating postgresql database");
using var connection = new NpgsqlConnection
(configuration.GetValue("DatabaseSettings:ConnectionString"));
connection.Open();
using var command = new NpgsqlCommand
{
Connection = connection
};
command.CommandText = @"create table if not exists Coupon
(Id serial primary key,
ProductName varchar(200) not null,
Description text,
Amount int)";
command.ExecuteNonQuery();
/*command.CommandText = @"insert into Coupon(ProductName, Description, Amount) values
('IPhone X', 'iphone discount', 150),
('Samsung 10', 'samsung discount', 150)";
command.ExecuteNonQuery();*/
logger.LogInformation("migration has been completed!");
}
catch (Exception ex)
{
logger.LogError($"an error has been occured: {ex.Message}");
if (retry < 50)
{
Thread.Sleep(2000);
host.MigrateDatabase(retry + 1);
}
}
return host;
}
}
================================================
FILE: Services/Discount/Discount.Grpc/Mapper/DiscountProfile.cs
================================================
using AutoMapper;
using Discount.Grpc.Entities;
using Discount.Grpc.Protos;
namespace Discount.Grpc.Mapper;
public class DiscountProfile : Profile
{
public DiscountProfile()
{
CreateMap().ReverseMap();
}
}
================================================
FILE: Services/Discount/Discount.Grpc/Program.cs
================================================
using Discount.Grpc.Extensions;
using Discount.Grpc.Repositories;
using Discount.Grpc.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddScoped();
var app = builder.Build();
app.MapGrpcService();
app.MigrateDatabase();
app.Run();
================================================
FILE: Services/Discount/Discount.Grpc/Properties/launchSettings.json
================================================
{
"profiles": {
"Discount.Grpc": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5003"
},
"Docker": {
"commandName": "Docker",
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8003"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json"
}
================================================
FILE: Services/Discount/Discount.Grpc/Protos/discount.proto
================================================
syntax = "proto3";
option csharp_namespace = "Discount.Grpc.Protos";
service DiscountProtoService {
rpc GetDiscount (GetDiscountRequest) returns (CouponModel);
rpc CreateDiscount (CreateDiscountRequest) returns (CouponModel);
rpc UpdateDiscount (UpdateDiscountRequest) returns (CouponModel);
rpc DeleteDiscount (DeleteDiscountRequest) returns (DeleteDiscountResponse);
}
message CouponModel {
int32 Id = 1;
string ProductName = 2;
string Description = 3;
int32 Amount = 4;
}
message GetDiscountRequest {
string ProductName = 1;
}
message CreateDiscountRequest {
CouponModel Coupon = 1;
}
message UpdateDiscountRequest {
CouponModel Coupon = 1;
}
message DeleteDiscountRequest {
string ProductName = 1;
}
message DeleteDiscountResponse {
bool Success = 1;
}
================================================
FILE: Services/Discount/Discount.Grpc/Repositories/CouponRepository.cs
================================================
using Dapper;
using Discount.Grpc.Entities;
using Npgsql;
namespace Discount.Grpc.Repositories;
public class CouponRepository(IConfiguration _configuration) : ICouponRepository
{
public async Task GetByProductName(string productName)
{
using var connection = new NpgsqlConnection
(_configuration.GetValue("DatabaseSettings:ConnectionString"));
var coupon = await connection.QueryFirstOrDefaultAsync
($"select * from Coupon where ProductName = '{productName}'");
if (coupon is null)
{
return new Coupon
{
ProductName = productName,
Description = "No discount",
Amount = 0
};
}
return coupon;
}
public async Task Create(Coupon coupon)
{
using var connection = new NpgsqlConnection
(_configuration.GetValue("DatabaseSettings:ConnectionString"));
var affected = await connection.ExecuteAsync
($"insert into Coupon(ProductName, Description, Amount) " +
$"values('{coupon.ProductName}', '{coupon.Description}', {coupon.Amount})");
return affected > 0;
}
public async Task Update(Coupon coupon)
{
using var connection = new NpgsqlConnection
(_configuration.GetValue("DatabaseSettings:ConnectionString"));
var affected = await connection.ExecuteAsync
($"update Coupon " +
$"set ProductName = '{coupon.ProductName}', Description = '{coupon.Description}', Amount = {coupon.Amount} " +
$"where Id = {coupon.Id}");
return affected > 0;
}
public async Task Delete(string productName)
{
using var connection = new NpgsqlConnection
(_configuration.GetValue("DatabaseSettings:ConnectionString"));
var affected = await connection.ExecuteAsync
($"delete from Coupon where ProductName = {productName}");
return affected > 0;
}
}
================================================
FILE: Services/Discount/Discount.Grpc/Repositories/ICouponRepository.cs
================================================
using Discount.Grpc.Entities;
namespace Discount.Grpc.Repositories;
public interface ICouponRepository
{
Task GetByProductName(string productName);
Task Create(Coupon coupon);
Task Update(Coupon coupon);
Task Delete(string productName);
}
================================================
FILE: Services/Discount/Discount.Grpc/Services/DiscountService.cs
================================================
using AutoMapper;
using Discount.Grpc.Entities;
using Discount.Grpc.Protos;
using Discount.Grpc.Repositories;
using Grpc.Core;
namespace Discount.Grpc.Services;
public class DiscountService(ICouponRepository _couponRepository, IMapper _mapper, ILogger _logger) : DiscountProtoService.DiscountProtoServiceBase
{
public async override Task GetDiscount(GetDiscountRequest request, ServerCallContext context)
{
var coupon = await _couponRepository.GetByProductName(request.ProductName);
_logger.LogInformation($"Get discount with product name {request.ProductName}");
return _mapper.Map(coupon);
}
public async override Task CreateDiscount(CreateDiscountRequest request, ServerCallContext context)
{
var coupon = _mapper.Map(request.Coupon);
await _couponRepository.Create(coupon);
_logger.LogInformation($"Create discount with product name {coupon.ProductName}");
return _mapper.Map(coupon);
}
public async override Task UpdateDiscount(UpdateDiscountRequest request, ServerCallContext context)
{
var coupon = _mapper.Map(request.Coupon);
await _couponRepository.Update(coupon);
_logger.LogInformation($"Update discount with product name {coupon.ProductName}");
return _mapper.Map(coupon);
}
public async override Task DeleteDiscount(DeleteDiscountRequest request, ServerCallContext context)
{
var result = await _couponRepository.Delete(request.ProductName);
_logger.LogInformation($"Delete discount with product name {request.ProductName}");
return new DeleteDiscountResponse
{
Success = result,
};
}
}
================================================
FILE: Services/Discount/Discount.Grpc/appsettings.Development.json
================================================
{
"DatabaseSettings": {
"ConnectionString": "Server=localhost;port=5432;Database=discountdb;User Id=admin;Password=admin1234;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
================================================
FILE: Services/Discount/Discount.Grpc/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
}
}
================================================
FILE: Services/Ordering/Ordering.Api/Controllers/OrderController.cs
================================================
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Ordering.Application.Features.Orders.Commands.CreateOrder;
using Ordering.Application.Features.Orders.Commands.DeleteOrder;
using Ordering.Application.Features.Orders.Commands.UpdateOrder;
using Ordering.Application.Features.Orders.Queries.GetOrdersList;
using System.Net;
namespace Ordering.Api.Controllers;
[ApiController]
[Route("api/v1/[controller]/[action]")]
public class OrderController(IMediator _mediator) : ControllerBase
{
[HttpGet("{userName}")]
[ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)]
public async Task>> GetOrdersByUserName(string userName)
{
var result = await _mediator.Send(new GetOrdersListQuery(userName));
return Ok(result);
}
[HttpPost]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
public async Task> CreateOrder([FromBody] CreateOrderCommand command)
{
var result = await _mediator.Send(command);
return Ok(result);
}
[HttpPut]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.NoContent)]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.NotFound)]
public async Task UpdateOrder([FromBody] UpdateOrderCommand command)
{
await _mediator.Send(command);
return NoContent();
}
[HttpDelete("{id}")]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.NoContent)]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.NotFound)]
public async Task DeleteOrder(int id)
{
await _mediator.Send(new DeleteOrderCommand(id));
return NoContent();
}
}
================================================
FILE: Services/Ordering/Ordering.Api/Dockerfile
================================================
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Services/Ordering/Ordering.Api/Ordering.Api.csproj", "Services/Ordering/Ordering.Api/"]
COPY ["BuildingBlocks/EventBus.Messages/EventBus.Messages.csproj", "BuildingBlocks/EventBus.Messages/"]
COPY ["Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj", "Services/Ordering/Ordering.Infrastructure/"]
COPY ["Services/Ordering/Ordering.Application/Ordering.Application.csproj", "Services/Ordering/Ordering.Application/"]
COPY ["Services/Ordering/Ordering.Domain/Ordering.Domain.csproj", "Services/Ordering/Ordering.Domain/"]
RUN dotnet restore "./Services/Ordering/Ordering.Api/./Ordering.Api.csproj"
COPY . .
WORKDIR "/src/Services/Ordering/Ordering.Api"
RUN dotnet build "./Ordering.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Ordering.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Ordering.Api.dll"]
================================================
FILE: Services/Ordering/Ordering.Api/EventBusConsumer/BasketCheckoutConsumer.cs
================================================
using AutoMapper;
using EventBus.Messages.Events;
using MassTransit;
using MediatR;
using Ordering.Application.Features.Orders.Commands.CreateOrder;
namespace Ordering.Api.EventBusConsumer;
public class BasketCheckoutConsumer(
IMapper _mapper,
IMediator _mediator,
ILogger _logger
) : IConsumer
{
public async Task Consume(ConsumeContext context)
{
var createOrderCommand = _mapper.Map(context.Message);
var result = await _mediator.Send(createOrderCommand);
_logger.LogInformation($"Checkout basket consumed by Order service with result Id = {result}");
}
}
================================================
FILE: Services/Ordering/Ordering.Api/HostExtensions.cs
================================================
using Microsoft.EntityFrameworkCore;
namespace Ordering.Api;
public static class HostExtensions
{
public static IHost MigrateDatabase(this IHost host,
Action seeder,
int? retry = 0)
where TContext : DbContext
{
var retryForAvailability = retry!.Value;
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
var logger = services.GetRequiredService>();
var context = services.GetService();
try
{
logger.LogInformation("migrating started for sql server");
InvokeSeeder(seeder, context, services);
logger.LogInformation("migrating finished for sql server");
}
catch (Exception ex)
{
logger.LogError($"migrating errored for sql server, {ex.Message}");
if (retryForAvailability < 50)
{
Thread.Sleep(2000);
MigrateDatabase(host, seeder, retryForAvailability + 1);
}
throw;
}
return host;
}
private static void InvokeSeeder(
Action seeder,
TContext context,
IServiceProvider services)
where TContext : DbContext
{
context.Database.Migrate();
seeder(context, services);
}
}
================================================
FILE: Services/Ordering/Ordering.Api/Mapper/CheckoutProfile.cs
================================================
using AutoMapper;
using EventBus.Messages.Events;
using Ordering.Application.Features.Orders.Commands.CreateOrder;
namespace Ordering.Api.Mapping;
public class CheckoutProfile : Profile
{
public CheckoutProfile()
{
CreateMap().ReverseMap();
}
}
================================================
FILE: Services/Ordering/Ordering.Api/Ordering.Api.csproj
================================================
net8.0
enable
enable
false
Linux
..\..\..
..\..\..\docker-compose.dcproj
================================================
FILE: Services/Ordering/Ordering.Api/Ordering.Api.http
================================================
@Ordering.Api_HostAddress = http://localhost:5116
GET {{Ordering.Api_HostAddress}}/weatherforecast/
Accept: application/json
###
================================================
FILE: Services/Ordering/Ordering.Api/Program.cs
================================================
using EventBus.Messages.Common;
using MassTransit;
using Ordering.Api;
using Ordering.Api.EventBusConsumer;
using Ordering.Application;
using Ordering.Infrastructure.Persistence;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddApplicationServices();
builder.Services.AddInfrastructureServices(builder.Configuration);
builder.Services.AddMassTransit(config =>
{
config.AddConsumer();
config.UsingRabbitMq((context, conf) =>
{
conf.Host(builder.Configuration.GetValue("EventBusSettings:HostAddress"));
conf.ReceiveEndpoint(EventBusConstant.BasketCheckoutQueue, c =>
{
c.ConfigureConsumer(context);
});
});
});
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddScoped();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapControllers();
app.MigrateDatabase((context, services) =>
{
var logger = services.GetService>();
OrderDBContextSeed.SeedAsync(context, logger).Wait();
});
app.Run();
================================================
FILE: Services/Ordering/Ordering.Api/Properties/launchSettings.json
================================================
{
"profiles": {
"Ordering.Api": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5004"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8004"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:54058",
"sslPort": 0
}
}
}
================================================
FILE: Services/Ordering/Ordering.Api/appsettings.Development.json
================================================
{
"ConnectionStrings": {
"OrderingConnectionString": "Server=localhost; Database=orderdb; User Id=sa; Password=Admin1234; TrustServerCertificate=True;"
},
"EventBusSettings": {
"HostAddress": "amqp://guest:guest@localhost:5672"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
================================================
FILE: Services/Ordering/Ordering.Api/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
================================================
FILE: Services/Ordering/Ordering.Application/ApplicationServiceRegistration.cs
================================================
using FluentValidation;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Ordering.Application.Behaviors;
using System.Reflection;
namespace Ordering.Application;
public static class ApplicationServiceRegistration
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
return services;
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Behaviors/UnhandledExceptionBehavior.cs
================================================
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Logging;
namespace Ordering.Application.Behaviors;
public class UnhandledExceptionBehavior(ILogger _logger)
: IPipelineBehavior where TRequest : IRequest
{
public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken)
{
try
{
return await next();
}
catch (Exception ex)
{
var requestName = typeof(TRequest).Name;
_logger.LogError(ex, $"Application unhandled exception for request {requestName}");
throw;
}
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Behaviors/ValidationBehavior.cs
================================================
using FluentValidation;
using MediatR;
namespace Ordering.Application.Behaviors;
public class ValidationBehavior(IEnumerable> _validators)
: IPipelineBehavior where TRequest : IRequest
{
public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken)
{
if (_validators.Any())
{
var content = new ValidationContext(request);
var validationResults = await Task.WhenAll(
_validators.Select(x => x.ValidateAsync(content, cancellationToken)));
var failures = validationResults.SelectMany(x => x.Errors).Where(x => x != null).ToList();
if (failures.Count != 0)
{
throw new Exceptions.ValidationException(failures);
}
}
return await next();
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Contracts/Infrastructure/IEmailService.cs
================================================
using Ordering.Application.Models;
namespace Ordering.Application.Contracts.Infrastructure;
public interface IEmailService
{
Task SendEmailAsync(Email email);
}
================================================
FILE: Services/Ordering/Ordering.Application/Contracts/Persistence/IAsyncRepository.cs
================================================
using Ordering.Domain.Common;
using System.Linq.Expressions;
namespace Ordering.Application.Contracts.Persistence;
public interface IAsyncRepository where T : EntityBase
{
Task> GetAllAsync();
Task> GetAsync(Expression> predicate);
Task> GetAsync(Expression>? predicate = null,
Func, IOrderedQueryable>? orderBy = null,
string? includeString = null,
bool disableTracking = true);
Task> GetAsync(Expression>? predicate = null,
Func, IOrderedQueryable>? orderBy = null,
List>>? includes = null,
bool disableTracking = true);
Task GetByIdAsync(int id);
Task CreateAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
}
================================================
FILE: Services/Ordering/Ordering.Application/Contracts/Persistence/IOrderRepository.cs
================================================
using Ordering.Domain.Entities;
namespace Ordering.Application.Contracts.Persistence;
public interface IOrderRepository : IAsyncRepository
{
Task> GetOrdersByUserName(string userName);
}
================================================
FILE: Services/Ordering/Ordering.Application/Exceptions/NotFoundException.cs
================================================
namespace Ordering.Application.Exceptions;
public class NotFoundException(string name, object key)
: ApplicationException($"Entity with name \"{name}\" and key \"{key}\" was not found")
{
}
================================================
FILE: Services/Ordering/Ordering.Application/Exceptions/ValidationException.cs
================================================
using FluentValidation.Results;
namespace Ordering.Application.Exceptions;
public class ValidationException : ApplicationException
{
public ValidationException()
: base("one or more validation errors has occured")
{
}
public ValidationException(IEnumerable failures) : this()
{
Errors = failures.GroupBy(x => x.PropertyName, x => x.ErrorMessage)
.ToDictionary(x => x.Key, x => x.ToList());
}
public IDictionary> Errors { get; set; } = new Dictionary>();
}
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Commands/CreateOrder/CreateOrderCommand.cs
================================================
using MediatR;
namespace Ordering.Application.Features.Orders.Commands.CreateOrder;
public sealed record CreateOrderCommand(
string UserName,
decimal TotalPrice,
string FirstName,
string LastName,
string EmailAddress,
string Country,
string City,
string BankName,
string RefCode,
int PaymentMethod
) : IRequest;
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs
================================================
using AutoMapper;
using MediatR;
using Microsoft.Extensions.Logging;
using Ordering.Application.Contracts.Infrastructure;
using Ordering.Application.Contracts.Persistence;
using Ordering.Application.Models;
using Ordering.Domain.Entities;
namespace Ordering.Application.Features.Orders.Commands.CreateOrder;
public class CreateOrderCommandHandler(IOrderRepository _orderRepository,
IMapper _mapper,
IEmailService _emailService,
ILogger _logger) : IRequestHandler
{
public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
var entity = _mapper.Map(request);
var result = await _orderRepository.CreateAsync(entity);
_logger.LogInformation($"order by Id {result.Id} created");
await SendMail(result);
return result.Id;
}
private async Task SendMail(Order order)
{
try
{
await _emailService.SendEmailAsync(new Email
{
To = "Test@Test.com",
Subject = "order added",
Body = "email Body"
});
}
catch (Exception ex)
{
_logger.LogError($"email has not been sent, {ex.Message}");
}
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Commands/CreateOrder/CreateOrderCommandValidator.cs
================================================
using FluentValidation;
namespace Ordering.Application.Features.Orders.Commands.CreateOrder;
public class CreateOrderCommandValidator : AbstractValidator
{
public CreateOrderCommandValidator()
{
RuleFor(x => x.UserName)
.NotNull().WithMessage("UserName is required")
.NotEmpty().WithMessage("UserName can not be empty")
.MinimumLength(3).WithMessage("UserName must be longer");
RuleFor(x => x.EmailAddress)
.NotEmpty().WithMessage("Email is required");
RuleFor(x => x.TotalPrice)
.NotEmpty().WithMessage("TotalPrice is required")
.GreaterThan(0).WithMessage("TotalPrice must be greater than zero");
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Commands/DeleteOrder/DeleteOrderCommand.cs
================================================
using MediatR;
namespace Ordering.Application.Features.Orders.Commands.DeleteOrder;
public sealed record DeleteOrderCommand(int Id) : IRequest;
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Commands/DeleteOrder/DeleteOrderCommandHandler.cs
================================================
using MediatR;
using Microsoft.Extensions.Logging;
using Ordering.Application.Contracts.Persistence;
using Ordering.Application.Exceptions;
using Ordering.Domain.Entities;
namespace Ordering.Application.Features.Orders.Commands.DeleteOrder
{
public class DeleteOrderCommandHandler(IOrderRepository _orderRepository,
ILogger _logger) : IRequestHandler
{
public async Task Handle(DeleteOrderCommand request, CancellationToken cancellationToken)
{
var order = await _orderRepository.GetByIdAsync(request.Id);
if (order is null)
{
_logger.LogError($"order with id {request.Id} does not exist");
throw new NotFoundException(nameof(Order), request.Id);
}
await _orderRepository.DeleteAsync(order);
_logger.LogInformation($"order by Id {order.Id} deleted");
}
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Commands/UpdateOrder/UpdateOrderCommand.cs
================================================
using MediatR;
namespace Ordering.Application.Features.Orders.Commands.UpdateOrder;
public sealed record UpdateOrderCommand(
int Id,
string UserName,
decimal TotalPrice,
string FirstName,
string LastName,
string EmailAddress,
string Country,
string City,
string BankName,
string RefCode,
int PaymentMethod
) : IRequest;
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Commands/UpdateOrder/UpdateOrderCommandHandler.cs
================================================
using AutoMapper;
using MediatR;
using Microsoft.Extensions.Logging;
using Ordering.Application.Contracts.Persistence;
using Ordering.Application.Exceptions;
using Ordering.Domain.Entities;
namespace Ordering.Application.Features.Orders.Commands.UpdateOrder;
public class UpdateOrderCommandHandler(IOrderRepository _orderRepository,
IMapper _mapper,
ILogger _logger) : IRequestHandler
{
public async Task Handle(UpdateOrderCommand request, CancellationToken cancellationToken)
{
var order = await _orderRepository.GetByIdAsync(request.Id);
if (order is null)
{
_logger.LogError($"order with id {request.Id} does not exist");
throw new NotFoundException(nameof(Order), request.Id);
}
_mapper.Map(request, order);
await _orderRepository.UpdateAsync(order);
_logger.LogInformation($"order by Id {order.Id} updated");
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Commands/UpdateOrder/UpdateOrderCommandValidator.cs
================================================
using FluentValidation;
namespace Ordering.Application.Features.Orders.Commands.UpdateOrder;
public class UpdateOrderCommandValidator : AbstractValidator
{
public UpdateOrderCommandValidator()
{
RuleFor(x => x.UserName)
.NotNull().WithMessage("UserName is required")
.NotEmpty().WithMessage("UserName can not be empty")
.MinimumLength(3).WithMessage("UserName must be longer");
RuleFor(x => x.EmailAddress)
.NotEmpty().WithMessage("Email is required");
RuleFor(x => x.TotalPrice)
.NotEmpty().WithMessage("TotalPrice is required")
.GreaterThan(0).WithMessage("TotalPrice must be greater than zero");
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Queries/GetOrdersList/GetOrdersListQuery.cs
================================================
using MediatR;
namespace Ordering.Application.Features.Orders.Queries.GetOrdersList;
public sealed record GetOrdersListQuery(string UserName) : IRequest>;
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Queries/GetOrdersList/GetOrdersListQueryHandler.cs
================================================
using AutoMapper;
using MediatR;
using Ordering.Application.Contracts.Persistence;
namespace Ordering.Application.Features.Orders.Queries.GetOrdersList;
public class GetOrdersListQueryHandler(IOrderRepository _orderRepository, IMapper _mapper) : IRequestHandler>
{
public async Task> Handle(GetOrdersListQuery request, CancellationToken cancellationToken)
{
var result = await _orderRepository.GetOrdersByUserName(request.UserName);
return _mapper.Map>(result);
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Features/Orders/Queries/GetOrdersList/OrderViewModel.cs
================================================
namespace Ordering.Application.Features.Orders.Queries.GetOrdersList;
public class OrderViewModel
{
public int Id { get; set; }
public string UserName { get; set; } = string.Empty;
public decimal TotalPrice { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string EmailAddress { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string BankName { get; set; } = string.Empty;
public string RefCode { get; set; } = string.Empty;
public int PaymentMethod { get; set; }
}
================================================
FILE: Services/Ordering/Ordering.Application/Mappings/MappingProfile.cs
================================================
using AutoMapper;
using Ordering.Application.Features.Orders.Commands.CreateOrder;
using Ordering.Application.Features.Orders.Commands.UpdateOrder;
using Ordering.Application.Features.Orders.Queries.GetOrdersList;
using Ordering.Domain.Entities;
namespace Ordering.Application.Mappings;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap().ReverseMap();
CreateMap().ReverseMap();
CreateMap().ReverseMap();
}
}
================================================
FILE: Services/Ordering/Ordering.Application/Models/Email.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ordering.Application.Models;
public class Email
{
public string To { get; set; } = string.Empty;
public string Subject { get; set; } = string.Empty;
public string Body { get; set; } = string.Empty;
}
================================================
FILE: Services/Ordering/Ordering.Application/Models/EmailSetting.cs
================================================
namespace Ordering.Application.Models;
public class EmailSetting
{
public string From { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public string UserName { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
================================================
FILE: Services/Ordering/Ordering.Application/Ordering.Application.csproj
================================================
net8.0
enable
enable
================================================
FILE: Services/Ordering/Ordering.Domain/Common/EntityBase.cs
================================================
namespace Ordering.Domain.Common;
public abstract class EntityBase
{
public int Id { get; set; }
public string CreatedBy { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public string UpdatedBy { get; set; } = string.Empty;
public DateTime? UpdatedAt { get; set; }
}
================================================
FILE: Services/Ordering/Ordering.Domain/Common/ValueObject.cs
================================================
namespace Ordering.Domain.Common;
public abstract class ValueObject
{
protected static bool EqualOperator(ValueObject left, ValueObject right)
{
if (left is null ^ right is null)
{
return false;
}
return ReferenceEquals(left, right) || left.Equals(right);
}
protected static bool NotEqualOperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
protected abstract IEnumerable