├── .github
├── arbems.jpg
└── icon.png
├── .gitignore
├── CleanArchitectureSolution.sln
├── README.md
├── src
├── Application
│ ├── Application.csproj
│ ├── Common
│ │ ├── Exceptions
│ │ │ ├── ForbiddenAccessException.cs
│ │ │ ├── NotFoundException.cs
│ │ │ └── ValidationException.cs
│ │ ├── Interfaces
│ │ │ ├── IApplicationDbContext.cs
│ │ │ ├── IDateTime.cs
│ │ │ └── IDomainEventService.cs
│ │ ├── Mappings
│ │ │ ├── IMapFrom.cs
│ │ │ ├── MappingExtensions.cs
│ │ │ └── MappingProfile.cs
│ │ └── Models
│ │ │ ├── DomainEventNotification.cs
│ │ │ ├── PaginatedList.cs
│ │ │ └── Result.cs
│ ├── DependencyInjection.cs
│ ├── SuperHeroes
│ │ ├── Commands
│ │ │ ├── CreateHero
│ │ │ │ ├── CreateHeroCommand.cs
│ │ │ │ └── CreateHeroCommandValidator.cs
│ │ │ ├── DeleteHero
│ │ │ │ └── DeleteHeroCommand.cs
│ │ │ └── UpdateHero
│ │ │ │ ├── UpdateHeroCommand.cs
│ │ │ │ └── UpdateHeroCommandValidator.cs
│ │ └── Queries
│ │ │ └── GetHeroesWithPagination
│ │ │ ├── AttributeDto.cs
│ │ │ ├── PowerDto.cs
│ │ │ ├── PublisherDto.cs
│ │ │ ├── RaceDto.cs
│ │ │ └── SuperheroDto.cs
│ └── Superheroes
│ │ ├── EventHandlers
│ │ ├── SuperheroCreatedEventHandler.cs
│ │ └── SuperheroDeletedEventHandler.cs
│ │ └── Queries
│ │ └── GetHeroesWithPagination
│ │ ├── GetSuperheroQuery.cs
│ │ └── GetSuperheroesWithPaginationQuery.cs
├── Domain
│ ├── Common
│ │ ├── AuditableEntity.cs
│ │ ├── DomainEvent.cs
│ │ └── ValueObject.cs
│ ├── Domain.csproj
│ ├── Entities
│ │ ├── Attribute.cs
│ │ ├── Power.cs
│ │ ├── Publisher.cs
│ │ ├── Race.cs
│ │ └── Superhero.cs
│ ├── Events
│ │ ├── SuperheroCreatedEvent.cs
│ │ └── SuperheroDeletedEvent.cs
│ ├── Exceptions
│ │ ├── UnsupportedAlignmentException.cs
│ │ ├── UnsupportedColourException.cs
│ │ └── UnsupportedGenderException.cs
│ └── ValueObjects
│ │ ├── Alignment.cs
│ │ ├── Colour.cs
│ │ └── Gender.cs
├── Infrastructure
│ ├── DependencyInjection.cs
│ ├── Infrastructure.csproj
│ ├── Persistence
│ │ ├── ApplicationDbContext.cs
│ │ ├── ApplicationDbContextSeed.cs
│ │ └── Configurations
│ │ │ ├── AttributeConfiguration.cs
│ │ │ ├── PowerConfiguration.cs
│ │ │ ├── PublisherConfiguration.cs
│ │ │ ├── RaceConfiguration.cs
│ │ │ └── SuperheroConfiguration.cs
│ └── Services
│ │ ├── DateTimeService.cs
│ │ └── DomainEventService.cs
└── PublicAPI
│ ├── Controllers
│ ├── ApiControllerBase.cs
│ └── SuperheroesController.cs
│ ├── Filters
│ └── ApiExceptionFilterAttribute.cs
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── PublicAPI.csproj
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ └── wwwroot
│ └── swagger-ui
│ └── custom.css
└── tests
├── Application.IntegrationTests
└── Application.IntegrationTests.csproj
├── Application.UnitTests
└── Application.UnitTests.csproj
└── Domain.UnitTests
└── Domain.UnitTests.csproj
/.github/arbems.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arbems/Clean-Architecture-Solution/9a8abc79583d74d13d329ec3059e3ace16560716/.github/arbems.jpg
--------------------------------------------------------------------------------
/.github/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arbems/Clean-Architecture-Solution/9a8abc79583d74d13d329ec3059e3ace16560716/.github/icon.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/CleanArchitectureSolution.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 25.0.1700.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{88F2B662-375A-4EB8-941B-2ADCEBF96416}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9E289593-5AA7-471F-80C1-6ED91210E3B3}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "src\Application\Application.csproj", "{A59D91DA-92AE-4139-B9C9-C5F33F77AF43}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{9025F4D8-14E5-4B1B-A634-156EB0C41379}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "src\Domain\Domain.csproj", "{AFA15A39-AABC-40A8-B227-A6A5D8738789}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application.IntegrationTests", "tests\Application.IntegrationTests\Application.IntegrationTests.csproj", "{14234959-DF2D-4EDF-A02C-F17C7AF684B1}"
17 | EndProject
18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application.UnitTests", "tests\Application.UnitTests\Application.UnitTests.csproj", "{004540C3-BD92-4593-814A-251D70D7F3FD}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain.UnitTests", "tests\Domain.UnitTests\Domain.UnitTests.csproj", "{A10C34B0-F784-4312-BCC2-75FF894BD803}"
21 | EndProject
22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublicAPI", "src\PublicAPI\PublicAPI.csproj", "{1C894B61-A86A-44DC-88D5-316318578EB6}"
23 | EndProject
24 | Global
25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
26 | Debug|Any CPU = Debug|Any CPU
27 | Release|Any CPU = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {A59D91DA-92AE-4139-B9C9-C5F33F77AF43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {A59D91DA-92AE-4139-B9C9-C5F33F77AF43}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {A59D91DA-92AE-4139-B9C9-C5F33F77AF43}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {A59D91DA-92AE-4139-B9C9-C5F33F77AF43}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {9025F4D8-14E5-4B1B-A634-156EB0C41379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {9025F4D8-14E5-4B1B-A634-156EB0C41379}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {9025F4D8-14E5-4B1B-A634-156EB0C41379}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {9025F4D8-14E5-4B1B-A634-156EB0C41379}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {AFA15A39-AABC-40A8-B227-A6A5D8738789}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {AFA15A39-AABC-40A8-B227-A6A5D8738789}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {AFA15A39-AABC-40A8-B227-A6A5D8738789}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {AFA15A39-AABC-40A8-B227-A6A5D8738789}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {14234959-DF2D-4EDF-A02C-F17C7AF684B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {14234959-DF2D-4EDF-A02C-F17C7AF684B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {14234959-DF2D-4EDF-A02C-F17C7AF684B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 | {14234959-DF2D-4EDF-A02C-F17C7AF684B1}.Release|Any CPU.Build.0 = Release|Any CPU
46 | {004540C3-BD92-4593-814A-251D70D7F3FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {004540C3-BD92-4593-814A-251D70D7F3FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {004540C3-BD92-4593-814A-251D70D7F3FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {004540C3-BD92-4593-814A-251D70D7F3FD}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {A10C34B0-F784-4312-BCC2-75FF894BD803}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | {A10C34B0-F784-4312-BCC2-75FF894BD803}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | {A10C34B0-F784-4312-BCC2-75FF894BD803}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {A10C34B0-F784-4312-BCC2-75FF894BD803}.Release|Any CPU.Build.0 = Release|Any CPU
54 | {1C894B61-A86A-44DC-88D5-316318578EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {1C894B61-A86A-44DC-88D5-316318578EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {1C894B61-A86A-44DC-88D5-316318578EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
57 | {1C894B61-A86A-44DC-88D5-316318578EB6}.Release|Any CPU.Build.0 = Release|Any CPU
58 | EndGlobalSection
59 | GlobalSection(SolutionProperties) = preSolution
60 | HideSolutionNode = FALSE
61 | EndGlobalSection
62 | GlobalSection(ExtensibilityGlobals) = postSolution
63 | SolutionGuid = {F6808069-24F5-46D9-A472-197C900BCD7E}
64 | EndGlobalSection
65 | GlobalSection(NestedProjects) = preSolution
66 | {A59D91DA-92AE-4139-B9C9-C5F33F77AF43} = {88F2B662-375A-4EB8-941B-2ADCEBF96416}
67 | {9025F4D8-14E5-4B1B-A634-156EB0C41379} = {88F2B662-375A-4EB8-941B-2ADCEBF96416}
68 | {AFA15A39-AABC-40A8-B227-A6A5D8738789} = {88F2B662-375A-4EB8-941B-2ADCEBF96416}
69 | {14234959-DF2D-4EDF-A02C-F17C7AF684B1} = {9E289593-5AA7-471F-80C1-6ED91210E3B3}
70 | {004540C3-BD92-4593-814A-251D70D7F3FD} = {9E289593-5AA7-471F-80C1-6ED91210E3B3}
71 | {A10C34B0-F784-4312-BCC2-75FF894BD803} = {9E289593-5AA7-471F-80C1-6ED91210E3B3}
72 | {1C894B61-A86A-44DC-88D5-316318578EB6} = {88F2B662-375A-4EB8-941B-2ADCEBF96416}
73 | EndGlobalSection
74 | EndGlobal
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Clean Architecture Solution .NET 6
4 | Example of a solution following the principles of Clean Architecture using .NET 6
5 |
6 | ## Tecnologías
7 | * NET 6 / C#
8 | * ASP.NET Core 6
9 | * Entity Framework Core 6
10 | * Angular 12
11 | * MediatR
12 | * AutoMapper
13 | * FluentValidation
14 | * NUnit, FluentAssertions, Moq & Respawn
15 | * Docker
16 |
--------------------------------------------------------------------------------
/src/Application/Application.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/Application/Common/Exceptions/ForbiddenAccessException.cs:
--------------------------------------------------------------------------------
1 | namespace Application.Common.Exceptions;
2 |
3 | public class ForbiddenAccessException : Exception
4 | {
5 | public ForbiddenAccessException() : base() { }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Application/Common/Exceptions/NotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace Application.Common.Exceptions;
2 |
3 | public class NotFoundException : Exception
4 | {
5 | public NotFoundException()
6 | : base()
7 | {
8 | }
9 |
10 | public NotFoundException(string message)
11 | : base(message)
12 | {
13 | }
14 |
15 | public NotFoundException(string message, Exception innerException)
16 | : base(message, innerException)
17 | {
18 | }
19 |
20 | public NotFoundException(string name, object key)
21 | : base($"Entity \"{name}\" ({key}) was not found.")
22 | {
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Application/Common/Exceptions/ValidationException.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation.Results;
2 |
3 | namespace Application.Common.Exceptions;
4 |
5 | public class ValidationException : Exception
6 | {
7 | public ValidationException()
8 | : base("One or more validation failures have occurred.")
9 | {
10 | Errors = new Dictionary();
11 | }
12 |
13 | public ValidationException(IEnumerable failures)
14 | : this()
15 | {
16 | Errors = failures
17 | .GroupBy(e => e.PropertyName, e => e.ErrorMessage)
18 | .ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
19 | }
20 |
21 | public IDictionary Errors { get; }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Application/Common/Interfaces/IApplicationDbContext.cs:
--------------------------------------------------------------------------------
1 | using Domain.Entities;
2 | using Microsoft.EntityFrameworkCore;
3 |
4 | namespace Application.Common.Interfaces;
5 |
6 | public interface IApplicationDbContext
7 | {
8 | public DbSet Attributes { get; }
9 | public DbSet Publishers { get; }
10 | public DbSet Races { get; }
11 | public DbSet Superheroes { get; }
12 | public DbSet Powers { get; }
13 |
14 | Task SaveChangesAsync(CancellationToken cancellationToken);
15 | }
16 |
--------------------------------------------------------------------------------
/src/Application/Common/Interfaces/IDateTime.cs:
--------------------------------------------------------------------------------
1 | namespace Application.Common.Interfaces;
2 |
3 | public interface IDateTime
4 | {
5 | DateTime Now { get; }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Application/Common/Interfaces/IDomainEventService.cs:
--------------------------------------------------------------------------------
1 | using Domain.Common;
2 |
3 | namespace Application.Common.Interfaces;
4 |
5 | public interface IDomainEventService
6 | {
7 | Task Publish(DomainEvent domainEvent);
8 | }
9 |
--------------------------------------------------------------------------------
/src/Application/Common/Mappings/IMapFrom.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 |
3 | namespace Application.Common.Mappings;
4 |
5 | public interface IMapFrom
6 | {
7 | void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
8 | }
9 |
--------------------------------------------------------------------------------
/src/Application/Common/Mappings/MappingExtensions.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Models;
2 | using AutoMapper;
3 | using AutoMapper.QueryableExtensions;
4 | using Microsoft.EntityFrameworkCore;
5 |
6 | namespace Application.Common.Mappings;
7 |
8 | public static class MappingExtensions
9 | {
10 | public static Task> PaginatedListAsync(this IQueryable queryable, int pageNumber, int pageSize)
11 | => PaginatedList.CreateAsync(queryable, pageNumber, pageSize);
12 |
13 | public static Task> ProjectToListAsync(this IQueryable queryable, IConfigurationProvider configuration)
14 | => queryable.ProjectTo(configuration).ToListAsync();
15 | }
16 |
--------------------------------------------------------------------------------
/src/Application/Common/Mappings/MappingProfile.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using AutoMapper;
3 |
4 | namespace Application.Common.Mappings;
5 |
6 | public class MappingProfile : Profile
7 | {
8 | public MappingProfile()
9 | {
10 | ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
11 | }
12 |
13 | private void ApplyMappingsFromAssembly(Assembly assembly)
14 | {
15 | var types = assembly.GetExportedTypes()
16 | .Where(t => t.GetInterfaces().Any(i =>
17 | i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
18 | .ToList();
19 |
20 | foreach (var type in types)
21 | {
22 | var instance = Activator.CreateInstance(type);
23 |
24 | var methodInfo = type.GetMethod("Mapping")
25 | ?? type.GetInterface("IMapFrom`1")!.GetMethod("Mapping");
26 |
27 | methodInfo?.Invoke(instance, new object[] { this });
28 |
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Application/Common/Models/DomainEventNotification.cs:
--------------------------------------------------------------------------------
1 | using Domain.Common;
2 | using MediatR;
3 |
4 | namespace Application.Common.Models;
5 |
6 | public class DomainEventNotification : INotification where TDomainEvent : DomainEvent
7 | {
8 | public DomainEventNotification(TDomainEvent domainEvent)
9 | {
10 | DomainEvent = domainEvent;
11 | }
12 |
13 | public TDomainEvent DomainEvent { get; }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Application/Common/Models/PaginatedList.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 |
3 | namespace Application.Common.Models;
4 |
5 | public class PaginatedList
6 | {
7 | public List Items { get; }
8 | public int PageNumber { get; }
9 | public int TotalPages { get; }
10 | public int TotalCount { get; }
11 |
12 | public PaginatedList(List items, int count, int pageNumber, int pageSize)
13 | {
14 | PageNumber = pageNumber;
15 | TotalPages = (int)Math.Ceiling(count / (double)pageSize);
16 | TotalCount = count;
17 | Items = items;
18 | }
19 |
20 | public bool HasPreviousPage => PageNumber > 1;
21 |
22 | public bool HasNextPage => PageNumber < TotalPages;
23 |
24 | public static async Task> CreateAsync(IQueryable source, int pageNumber, int pageSize)
25 | {
26 | var count = await source.CountAsync();
27 | var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
28 |
29 | return new PaginatedList(items, count, pageNumber, pageSize);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Application/Common/Models/Result.cs:
--------------------------------------------------------------------------------
1 | namespace Application.Common.Models;
2 |
3 | public class Result
4 | {
5 | internal Result(bool succeeded, IEnumerable errors)
6 | {
7 | Succeeded = succeeded;
8 | Errors = errors.ToArray();
9 | }
10 |
11 | public bool Succeeded { get; set; }
12 |
13 | public string[] Errors { get; set; }
14 |
15 | public static Result Success()
16 | {
17 | return new Result(true, Array.Empty());
18 | }
19 |
20 | public static Result Failure(IEnumerable errors)
21 | {
22 | return new Result(false, errors);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Application/DependencyInjection.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using MediatR;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using System.Reflection;
5 |
6 | namespace Application;
7 |
8 | public static class DependencyInjection
9 | {
10 | public static IServiceCollection AddApplication(this IServiceCollection services)
11 | {
12 | services.AddAutoMapper(Assembly.GetExecutingAssembly());
13 | services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
14 | services.AddMediatR(Assembly.GetExecutingAssembly());
15 |
16 | return services;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Commands/CreateHero/CreateHeroCommand.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Interfaces;
2 | using Domain.Entities;
3 | using Domain.Events;
4 | using Domain.ValueObjects;
5 | using MediatR;
6 |
7 | namespace Application.Superheroes.Commands.CreateHero;
8 |
9 | public class CreateHeroCommand : IRequest
10 | {
11 | public string SuperheroName { get; set; } = String.Empty;
12 | public string? FullName { get; set; } = String.Empty;
13 | public int? HeightCm { get; set; }
14 | public int? WeightKg { get; set; }
15 |
16 | public string? EyeColour { get; set; }
17 | public string? HairColour { get; set; }
18 | public string? SkinColour { get; set; }
19 | public string? Alignment { get; set; }
20 | public string? Gender { get; set; }
21 |
22 | public int? PublisherId { get; set; }
23 |
24 | public int? RaceId { get; set; }
25 |
26 | public List Attributes { get; set; } = new();
27 | public List Powers { get; set; } = new();
28 | }
29 |
30 | public class CreateHeroCommandHandler : IRequestHandler
31 | {
32 | private readonly IApplicationDbContext _context;
33 |
34 | public CreateHeroCommandHandler(IApplicationDbContext context)
35 | {
36 | _context = context;
37 | }
38 |
39 | public async Task Handle(CreateHeroCommand request, CancellationToken cancellationToken)
40 | {
41 | Superhero entity = new()
42 | {
43 | SuperheroName = request.SuperheroName,
44 | FullName = request.FullName,
45 | HeightCm = request.HeightCm,
46 | WeightKg = request.WeightKg,
47 | EyeColour = string.IsNullOrEmpty(request.EyeColour) ? Colour.NoColour : Colour.From(request.EyeColour),
48 | HairColour = string.IsNullOrEmpty(request.HairColour) ? Colour.NoColour : Colour.From(request.HairColour),
49 | SkinColour = string.IsNullOrEmpty(request.SkinColour) ? Colour.NoColour : Colour.From(request.SkinColour),
50 | Alignment = string.IsNullOrEmpty(request.Alignment) ? Alignment.NA : Alignment.From(request.Alignment),
51 | Gender = string.IsNullOrEmpty(request.Gender) ? Gender.NA : Gender.From(request.Gender),
52 | Publisher = _context.Publishers.FindAsync(request.PublisherId).Result,
53 | Race = _context.Races.FindAsync(request.RaceId).Result,
54 | Attributes = _context.Attributes.Where(a => request.Attributes.Contains(a.Id)).ToList(),
55 | Powers = _context.Powers.Where(a => request.Powers.Contains(a.Id)).ToList()
56 | };
57 |
58 | entity.DomainEvents.Add(new SuperheroCreatedEvent(entity));
59 |
60 | _context.Superheroes.Add(entity);
61 |
62 | await _context.SaveChangesAsync(cancellationToken);
63 |
64 | return entity.Id;
65 | }
66 | }
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Commands/CreateHero/CreateHeroCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 |
3 | namespace Application.Superheroes.Commands.CreateHero;
4 |
5 | public class CreateHeroCommandValidator : AbstractValidator
6 | {
7 | public CreateHeroCommandValidator()
8 | {
9 | RuleFor(v => v.SuperheroName)
10 | .MaximumLength(200)
11 | .NotEmpty();
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Commands/DeleteHero/DeleteHeroCommand.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Exceptions;
2 | using Application.Common.Interfaces;
3 | using Domain.Entities;
4 | using Domain.Events;
5 | using MediatR;
6 |
7 | namespace Application.Superheroes.Commands.DeleteHero;
8 |
9 | public class DeleteSuperheroCommand : IRequest
10 | {
11 | public int Id { get; set; }
12 | }
13 |
14 | public class DeleteSuperheroCommandHandler : IRequestHandler
15 | {
16 | private readonly IApplicationDbContext _context;
17 |
18 | public DeleteSuperheroCommandHandler(IApplicationDbContext context)
19 | {
20 | _context = context;
21 | }
22 |
23 | public async Task Handle(DeleteSuperheroCommand request, CancellationToken cancellationToken)
24 | {
25 | var entity = await _context.Superheroes
26 | .FindAsync(new object[] { request.Id }, cancellationToken);
27 |
28 | if (entity == null)
29 | {
30 | throw new NotFoundException(nameof(Superhero), request.Id);
31 | }
32 |
33 | _context.Superheroes.Remove(entity);
34 |
35 | entity.DomainEvents.Add(new SuperheroDeletedEvent(entity));
36 |
37 | await _context.SaveChangesAsync(cancellationToken);
38 |
39 | return Unit.Value;
40 | }
41 | }
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Commands/UpdateHero/UpdateHeroCommand.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Exceptions;
2 | using Application.Common.Interfaces;
3 | using Domain.Entities;
4 | using Domain.ValueObjects;
5 | using MediatR;
6 |
7 | namespace Application.Superheroes.Commands.UpdateHero;
8 |
9 | public class UpdateHeroCommand : IRequest
10 | {
11 | public int Id { get; set; }
12 | public string SuperheroName { get; set; } = String.Empty;
13 | public string? FullName { get; set; } = String.Empty;
14 | public int? HeightCm { get; set; }
15 | public int? WeightKg { get; set; }
16 |
17 | public string? EyeColour { get; set; }
18 | public string? HairColour { get; set; }
19 | public string? SkinColour { get; set; }
20 | public string? Alignment { get; set; }
21 | public string? Gender { get; set; }
22 |
23 | public int? PublisherId { get; set; }
24 |
25 | public int? RaceId { get; set; }
26 |
27 | public List Attributes { get; set; } = new();
28 | public List Powers { get; set; } = new();
29 | }
30 |
31 | public class UpdateHeroCommandHandler : IRequestHandler
32 | {
33 | private readonly IApplicationDbContext _context;
34 |
35 | public UpdateHeroCommandHandler(IApplicationDbContext context)
36 | {
37 | _context = context;
38 | }
39 |
40 | public async Task Handle(UpdateHeroCommand request, CancellationToken cancellationToken)
41 | {
42 | var entity = await _context.Superheroes
43 | .FindAsync(new object[] { request.Id }, cancellationToken);
44 |
45 | if (entity == null)
46 | {
47 | throw new NotFoundException(nameof(Superhero), request.Id);
48 | }
49 |
50 | entity.SuperheroName = request.SuperheroName;
51 | entity.FullName = request.FullName;
52 | entity.HeightCm = request.HeightCm;
53 | entity.WeightKg = request.WeightKg;
54 | entity.EyeColour = string.IsNullOrEmpty(request.EyeColour) ? Colour.NoColour : Colour.From(request.EyeColour);
55 | entity.HairColour = string.IsNullOrEmpty(request.HairColour) ? Colour.NoColour : Colour.From(request.HairColour);
56 | entity.SkinColour = string.IsNullOrEmpty(request.SkinColour) ? Colour.NoColour : Colour.From(request.SkinColour);
57 | entity.Alignment = string.IsNullOrEmpty(request.Alignment) ? Alignment.NA : Alignment.From(request.Alignment);
58 | entity.Gender = string.IsNullOrEmpty(request.Gender) ? Gender.NA : Gender.From(request.Gender);
59 | entity.Publisher = _context.Publishers.FindAsync(request.PublisherId).Result;
60 | entity.Race = _context.Races.FindAsync(request.RaceId).Result;
61 | entity.Attributes = _context.Attributes.Where(a => request.Attributes.Contains(a.Id)).ToList();
62 | entity.Powers = _context.Powers.Where(a => request.Powers.Contains(a.Id)).ToList();
63 |
64 | await _context.SaveChangesAsync(cancellationToken);
65 |
66 | return Unit.Value;
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Commands/UpdateHero/UpdateHeroCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 |
3 | namespace Application.Superheroes.Commands.UpdateHero;
4 |
5 | public class UpdateHeroCommandValidator : AbstractValidator
6 | {
7 | public UpdateHeroCommandValidator()
8 | {
9 | RuleFor(v => v.SuperheroName)
10 | .MaximumLength(200)
11 | .NotEmpty();
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Queries/GetHeroesWithPagination/AttributeDto.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Mappings;
2 |
3 | namespace Application.Superheroes.Queries.GetHeroesWithPagination;
4 |
5 | public class AttributeDto : IMapFrom
6 | {
7 | public int Id { get; set; }
8 | public string? AttributeName { get; set; }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Queries/GetHeroesWithPagination/PowerDto.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Mappings;
2 | using Domain.Entities;
3 |
4 | namespace Application.Superheroes.Queries.GetHeroesWithPagination;
5 |
6 | public class PowerDto : IMapFrom
7 | {
8 | public int Id { get; set; }
9 | public string PowerName { get; set; }
10 | }
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Queries/GetHeroesWithPagination/PublisherDto.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Mappings;
2 | using Domain.Entities;
3 |
4 | namespace Application.Superheroes.Queries.GetHeroesWithPagination;
5 |
6 | public class PublisherDto : IMapFrom
7 | {
8 | public int Id { get; set; }
9 | public string? PublisherName { get; set; }
10 | }
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Queries/GetHeroesWithPagination/RaceDto.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Mappings;
2 | using Domain.Entities;
3 |
4 | namespace Application.Superheroes.Queries.GetHeroesWithPagination;
5 |
6 | public class RaceDto : IMapFrom
7 | {
8 | public int Id { get; set; }
9 | public string? RaceName { get; set; }
10 | }
--------------------------------------------------------------------------------
/src/Application/SuperHeroes/Queries/GetHeroesWithPagination/SuperheroDto.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Mappings;
2 | using Domain.Entities;
3 | using Domain.ValueObjects;
4 |
5 | namespace Application.Superheroes.Queries.GetHeroesWithPagination;
6 |
7 | public class SuperheroDto : IMapFrom
8 | {
9 | public int Id { get; set; }
10 | public string SuperheroName { get; set; } = String.Empty;
11 | public string FullName { get; set; } = String.Empty;
12 | public int? HeightCm { get; set; }
13 | public int? WeightKg { get; set; }
14 |
15 | public Colour? EyeColour { get; set; } = Colour.NoColour;
16 | public Colour? HairColour { get; set; } = Colour.NoColour;
17 | public Colour? SkinColour { get; set; } = Colour.NoColour;
18 | public Alignment? Alignment { get; set; } = Alignment.NA;
19 | public Gender? Gender { get; set; } = Gender.NA;
20 |
21 | public PublisherDto? Publisher { get; set; }
22 |
23 | public RaceDto? Race { get; set; }
24 |
25 | public List Attributes { get; set; } = new();
26 | public List Powers { get; set; } = new();
27 |
28 | public DateTime Created { get; set; }
29 | public string? CreatedBy { get; set; }
30 | public DateTime? LastModified { get; set; }
31 | public string? LastModifiedBy { get; set; }
32 | public Guid RowVersion { get; set; }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Application/Superheroes/EventHandlers/SuperheroCreatedEventHandler.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Models;
2 | using Domain.Events;
3 | using MediatR;
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace Application.Superheroes.EventHandlers;
7 |
8 | public class SuperheroCreatedEventHandler : INotificationHandler>
9 | {
10 | private readonly ILogger _logger;
11 |
12 | public SuperheroCreatedEventHandler(ILogger logger)
13 | {
14 | _logger = logger;
15 | }
16 |
17 | public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken)
18 | {
19 | var domainEvent = notification.DomainEvent;
20 |
21 | _logger.LogInformation("Domain Event: {DomainEvent}", domainEvent.GetType().Name);
22 |
23 | return Task.CompletedTask;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/Application/Superheroes/EventHandlers/SuperheroDeletedEventHandler.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Models;
2 | using Domain.Events;
3 | using MediatR;
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace Application.Superheroes.EventHandlers;
7 |
8 | public class SuperheroDeletedEventHandler : INotificationHandler>
9 | {
10 | private readonly ILogger _logger;
11 |
12 | public SuperheroDeletedEventHandler(ILogger logger)
13 | {
14 | _logger = logger;
15 | }
16 |
17 | public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken)
18 | {
19 | var domainEvent = notification.DomainEvent;
20 |
21 | _logger.LogInformation("Domain Event: {DomainEvent}", domainEvent.GetType().Name);
22 |
23 | return Task.CompletedTask;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/Application/Superheroes/Queries/GetHeroesWithPagination/GetSuperheroQuery.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Interfaces;
2 | using AutoMapper;
3 | using Domain.Entities;
4 | using MediatR;
5 |
6 | namespace Application.Superheroes.Queries.GetHeroesWithPagination;
7 |
8 | public class GetSuperheroQuery : IRequest
9 | {
10 | public int Id { get; set; }
11 | }
12 |
13 | public class GetSuperheroQueryHandler : IRequestHandler
14 | {
15 | private readonly IApplicationDbContext _context;
16 | private readonly IMapper _mapper;
17 |
18 | public GetSuperheroQueryHandler(IApplicationDbContext context, IMapper mapper)
19 | {
20 | _context = context;
21 | _mapper = mapper;
22 | }
23 |
24 | public async Task Handle(GetSuperheroQuery request, CancellationToken cancellationToken)
25 | {
26 | var entity = await _context.Superheroes.FindAsync(request.Id);
27 |
28 | return _mapper.Map(entity!);
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/src/Application/Superheroes/Queries/GetHeroesWithPagination/GetSuperheroesWithPaginationQuery.cs:
--------------------------------------------------------------------------------
1 | using Application.Common.Interfaces;
2 | using Application.Common.Mappings;
3 | using Application.Common.Models;
4 | using Application.Superheroes.Queries.GetHeroesWithPagination;
5 | using AutoMapper;
6 | using AutoMapper.QueryableExtensions;
7 | using MediatR;
8 | using Microsoft.EntityFrameworkCore;
9 |
10 | namespace Application.Superheroes.Queries.GetHeroesWithPagination;
11 |
12 | public class GetSuperheroesWithPaginationQuery : IRequest>
13 | {
14 | public int PageNumber { get; set; } = 1;
15 | public int PageSize { get; set; } = 10;
16 | }
17 |
18 | public class GetSuperheroesWithPaginationQueryHandler : IRequestHandler>
19 | {
20 | private readonly IApplicationDbContext _context;
21 | private readonly IMapper _mapper;
22 |
23 | public GetSuperheroesWithPaginationQueryHandler(IApplicationDbContext context, IMapper mapper)
24 | {
25 | _context = context;
26 | _mapper = mapper;
27 | }
28 |
29 | public async Task> Handle(GetSuperheroesWithPaginationQuery request, CancellationToken cancellationToken)
30 | {
31 | return await _context.Superheroes.AsNoTracking()
32 | .OrderBy(x => x.SuperheroName)
33 | .ProjectTo(_mapper.ConfigurationProvider)
34 | .PaginatedListAsync(request.PageNumber, request.PageSize);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Domain/Common/AuditableEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace Domain.Common
5 | {
6 | public abstract class AuditableEntity
7 | {
8 | public DateTime Created { get; set; }
9 |
10 | public string? CreatedBy { get; set; }
11 |
12 | public DateTime? LastModified { get; set; }
13 |
14 | public string? LastModifiedBy { get; set; }
15 |
16 | public Guid RowVersion { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Domain/Common/DomainEvent.cs:
--------------------------------------------------------------------------------
1 | namespace Domain.Common;
2 |
3 | public interface IHasDomainEvent
4 | {
5 | public List DomainEvents { get; set; }
6 | }
7 |
8 | public abstract class DomainEvent
9 | {
10 | protected DomainEvent()
11 | {
12 | DateOccurred = DateTimeOffset.UtcNow;
13 | }
14 | public bool IsPublished { get; set; }
15 | public DateTimeOffset DateOccurred { get; protected set; } = DateTime.UtcNow;
16 | }
17 |
--------------------------------------------------------------------------------
/src/Domain/Common/ValueObject.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace Domain.Common
5 | {
6 | public abstract class ValueObject
7 | {
8 | protected static bool EqualOperator(ValueObject left, ValueObject right)
9 | {
10 | if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
11 | {
12 | return false;
13 | }
14 | return ReferenceEquals(left, null) || left.Equals(right);
15 | }
16 |
17 | protected static bool NotEqualOperator(ValueObject left, ValueObject right)
18 | {
19 | return !(EqualOperator(left, right));
20 | }
21 |
22 | protected abstract IEnumerable