├── .dockerignore
├── .github
└── workflows
│ └── dotnet.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── Sample.sln
├── docker-compose.yml
├── src
├── Application
│ ├── Application.csproj
│ ├── Dtos
│ │ └── EducationDto.cs
│ ├── Service
│ │ ├── EducationService.cs
│ │ └── Interfaces
│ │ │ └── IEducationService.cs
│ └── UoW
│ │ └── IUnitOfWork.cs
├── Domain
│ ├── Common
│ │ ├── AuditableEntityBase.cs
│ │ └── EntityBase.cs
│ ├── Domain.csproj
│ ├── Entities
│ │ └── Education.cs
│ └── Interfaces
│ │ └── IEducationRepository.cs
├── Infrastructure
│ ├── Configurations
│ │ └── EducationConfiguration.cs
│ ├── DbContexts
│ │ └── SampleDbContext.cs
│ ├── Infrastructure.csproj
│ ├── Migrations
│ │ ├── 20240113141226_Initialize.Designer.cs
│ │ ├── 20240113141226_Initialize.cs
│ │ └── SampleDbContextModelSnapshot.cs
│ ├── Repositories
│ │ └── EducationRepository.cs
│ └── UoW
│ │ └── UnitOfWork.cs
└── WebApi
│ ├── Controllers
│ └── EducationsController.cs
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── WebApi.csproj
│ ├── appsettings.Development.json
│ └── appsettings.json
└── tests
├── Application.Tests
├── Application.Tests.csproj
├── EducationServiceTests.cs
└── Usings.cs
└── WebApi.Tests
├── EducationsControllerTests.cs
├── Usings.cs
└── WebApi.Tests.csproj
/.dockerignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/csharp
3 |
4 | ### Csharp ###
5 | ## Ignore Visual Studio temporary files, build results, and
6 | ## files generated by popular Visual Studio add-ons.
7 |
8 | # User-specific files
9 | *.suo
10 | *.user
11 | *.userosscache
12 | *.sln.docstates
13 |
14 | # User-specific files (MonoDevelop/Xamarin Studio)
15 | *.userprefs
16 |
17 | # Build results
18 | [Dd]ebug/
19 | [Dd]ebugPublic/
20 | [Rr]elease/
21 | [Rr]eleases/
22 | x64/
23 | x86/
24 | bld/
25 | [Bb]in/
26 | [Oo]bj/
27 | [Ll]og/
28 |
29 | # Visual Studio cache/options directory
30 | .vs/
31 | # Uncomment if you have tasks that create the project's static files in wwwroot
32 | #wwwroot/
33 |
34 | # MSTest test Results
35 | [Tt]est[Rr]esult*/
36 | [Bb]uild[Ll]og.*
37 |
38 | # NUNIT
39 | *.VisualState.xml
40 | TestResult.xml
41 |
42 | # Build Results of an ATL Project
43 | [Dd]ebugPS/
44 | [Rr]eleasePS/
45 | dlldata.c
46 |
47 | # DNX
48 | project.lock.json
49 | project.fragment.lock.json
50 | artifacts/
51 | Properties/launchSettings.json
52 |
53 | *_i.c
54 | *_p.c
55 | *_i.h
56 | *.ilk
57 | *.meta
58 | *.obj
59 | *.pch
60 | *.pdb
61 | *.pgc
62 | *.pgd
63 | *.rsp
64 | *.sbr
65 | *.tlb
66 | *.tli
67 | *.tlh
68 | *.tmp
69 | *.tmp_proj
70 | *.log
71 | *.vspscc
72 | *.vssscc
73 | .builds
74 | *.pidb
75 | *.svclog
76 | *.scc
77 |
78 | # Chutzpah Test files
79 | _Chutzpah*
80 |
81 | # Visual C++ cache files
82 | ipch/
83 | *.aps
84 | *.ncb
85 | *.opendb
86 | *.opensdf
87 | *.sdf
88 | *.cachefile
89 | *.VC.db
90 | *.VC.VC.opendb
91 |
92 | # Visual Studio profiler
93 | *.psess
94 | *.vsp
95 | *.vspx
96 | *.sap
97 |
98 | # TFS 2012 Local Workspace
99 | $tf/
100 |
101 | # Guidance Automation Toolkit
102 | *.gpState
103 |
104 | # ReSharper is a .NET coding add-in
105 | _ReSharper*/
106 | *.[Rr]e[Ss]harper
107 | *.DotSettings.user
108 |
109 | # JustCode is a .NET coding add-in
110 | .JustCode
111 |
112 | # TeamCity is a build add-in
113 | _TeamCity*
114 |
115 | # DotCover is a Code Coverage Tool
116 | *.dotCover
117 |
118 | # Visual Studio code coverage results
119 | *.coverage
120 | *.coveragexml
121 |
122 | # NCrunch
123 | _NCrunch_*
124 | .*crunch*.local.xml
125 | nCrunchTemp_*
126 |
127 | # MightyMoose
128 | *.mm.*
129 | AutoTest.Net/
130 |
131 | # Web workbench (sass)
132 | .sass-cache/
133 |
134 | # Installshield output folder
135 | [Ee]xpress/
136 |
137 | # DocProject is a documentation generator add-in
138 | DocProject/buildhelp/
139 | DocProject/Help/*.HxT
140 | DocProject/Help/*.HxC
141 | DocProject/Help/*.hhc
142 | DocProject/Help/*.hhk
143 | DocProject/Help/*.hhp
144 | DocProject/Help/Html2
145 | DocProject/Help/html
146 |
147 | # Click-Once directory
148 | publish/
149 |
150 | # Publish Web Output
151 | *.[Pp]ublish.xml
152 | *.azurePubxml
153 | # TODO: Comment the next line if you want to checkin your web deploy settings
154 | # but database connection strings (with potential passwords) will be unencrypted
155 | *.pubxml
156 | *.publishproj
157 |
158 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
159 | # checkin your Azure Web App publish settings, but sensitive information contained
160 | # in these scripts will be unencrypted
161 | PublishScripts/
162 |
163 | # NuGet Packages
164 | *.nupkg
165 | # The packages folder can be ignored because of Package Restore
166 | **/packages/*
167 | # except build/, which is used as an MSBuild target.
168 | !**/packages/build/
169 | # Uncomment if necessary however generally it will be regenerated when needed
170 | #!**/packages/repositories.config
171 | # NuGet v3's project.json files produces more ignoreable files
172 | *.nuget.props
173 | *.nuget.targets
174 |
175 | # Microsoft Azure Build Output
176 | csx/
177 | *.build.csdef
178 |
179 | # Microsoft Azure Emulator
180 | ecf/
181 | rcf/
182 |
183 | # Windows Store app package directories and files
184 | AppPackages/
185 | BundleArtifacts/
186 | Package.StoreAssociation.xml
187 | _pkginfo.txt
188 |
189 | # Visual Studio cache files
190 | # files ending in .cache can be ignored
191 | *.[Cc]ache
192 | # but keep track of directories ending in .cache
193 | !*.[Cc]ache/
194 |
195 | # Others
196 | ClientBin/
197 | ~$*
198 | *~
199 | *.dbmdl
200 | *.dbproj.schemaview
201 | *.jfm
202 | *.pfx
203 | *.publishsettings
204 | node_modules/
205 | orleans.codegen.cs
206 |
207 | # Since there are multiple workflows, uncomment next line to ignore bower_components
208 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
209 | #bower_components/
210 |
211 | # RIA/Silverlight projects
212 | Generated_Code/
213 |
214 | # Backup & report files from converting an old project file
215 | # to a newer Visual Studio version. Backup files are not needed,
216 | # because we have git ;-)
217 | _UpgradeReport_Files/
218 | Backup*/
219 | UpgradeLog*.XML
220 | UpgradeLog*.htm
221 |
222 | # SQL Server files
223 | *.mdf
224 | *.ldf
225 |
226 | # Business Intelligence projects
227 | *.rdl.data
228 | *.bim.layout
229 | *.bim_*.settings
230 |
231 | # Microsoft Fakes
232 | FakesAssemblies/
233 |
234 | # GhostDoc plugin setting file
235 | *.GhostDoc.xml
236 |
237 | # Node.js Tools for Visual Studio
238 | .ntvs_analysis.dat
239 |
240 | # Visual Studio 6 build log
241 | *.plg
242 |
243 | # Visual Studio 6 workspace options file
244 | *.opt
245 |
246 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
247 | *.vbw
248 |
249 | # Visual Studio LightSwitch build output
250 | **/*.HTMLClient/GeneratedArtifacts
251 | **/*.DesktopClient/GeneratedArtifacts
252 | **/*.DesktopClient/ModelManifest.xml
253 | **/*.Server/GeneratedArtifacts
254 | **/*.Server/ModelManifest.xml
255 | _Pvt_Extensions
256 |
257 | # Paket dependency manager
258 | .paket/paket.exe
259 | paket-files/
260 |
261 | # FAKE - F# Make
262 | .fake/
263 |
264 | # JetBrains Rider
265 | .idea/
266 | *.sln.iml
267 |
268 | # CodeRush
269 | .cr/
270 |
271 | # Python Tools for Visual Studio (PTVS)
272 | __pycache__/
273 | *.pyc
274 |
275 | # Cake - Uncomment if you are using it
276 | # tools/
277 | tools/Cake.CoreCLR
278 | .vscode
279 | tools
280 | .dotnet
281 | Dockerfile
282 |
283 | # .env file contains default environment variables for docker
284 | .env
285 | .git/
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a .NET project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
3 |
4 | name: .NET
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | pull_request:
10 | branches: [ "main" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Setup .NET
20 | uses: actions/setup-dotnet@v3
21 | with:
22 | dotnet-version: 6.0.x
23 | - name: Restore dependencies
24 | run: dotnet restore
25 | - name: Build
26 | run: dotnet build --no-restore
27 | - name: Test
28 | run: dotnet test --no-build --verbosity normal
29 |
--------------------------------------------------------------------------------
/.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/main/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 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
298 | *.vbp
299 |
300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
301 | *.dsw
302 | *.dsp
303 |
304 | # Visual Studio 6 technical files
305 | *.ncb
306 | *.aps
307 |
308 | # Visual Studio LightSwitch build output
309 | **/*.HTMLClient/GeneratedArtifacts
310 | **/*.DesktopClient/GeneratedArtifacts
311 | **/*.DesktopClient/ModelManifest.xml
312 | **/*.Server/GeneratedArtifacts
313 | **/*.Server/ModelManifest.xml
314 | _Pvt_Extensions
315 |
316 | # Paket dependency manager
317 | .paket/paket.exe
318 | paket-files/
319 |
320 | # FAKE - F# Make
321 | .fake/
322 |
323 | # CodeRush personal settings
324 | .cr/personal
325 |
326 | # Python Tools for Visual Studio (PTVS)
327 | __pycache__/
328 | *.pyc
329 |
330 | # Cake - Uncomment if you are using it
331 | # tools/**
332 | # !tools/packages.config
333 |
334 | # Tabs Studio
335 | *.tss
336 |
337 | # Telerik's JustMock configuration file
338 | *.jmconfig
339 |
340 | # BizTalk build output
341 | *.btp.cs
342 | *.btm.cs
343 | *.odx.cs
344 | *.xsd.cs
345 |
346 | # OpenCover UI analysis results
347 | OpenCover/
348 |
349 | # Azure Stream Analytics local run output
350 | ASALocalRun/
351 |
352 | # MSBuild Binary and Structured Log
353 | *.binlog
354 |
355 | # NVidia Nsight GPU debugger configuration file
356 | *.nvuser
357 |
358 | # MFractors (Xamarin productivity tool) working folder
359 | .mfractor/
360 |
361 | # Local History for Visual Studio
362 | .localhistory/
363 |
364 | # Visual Studio History (VSHistory) files
365 | .vshistory/
366 |
367 | # BeatPulse healthcheck temp database
368 | healthchecksdb
369 |
370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
371 | MigrationBackup/
372 |
373 | # Ionide (cross platform F# VS Code tools) working folder
374 | .ionide/
375 |
376 | # Fody - auto-generated XML schema
377 | FodyWeavers.xsd
378 |
379 | # VS Code files for those working on multiple tools
380 | .vscode/*
381 | !.vscode/settings.json
382 | !.vscode/tasks.json
383 | !.vscode/launch.json
384 | !.vscode/extensions.json
385 | *.code-workspace
386 |
387 | # Local History for Visual Studio Code
388 | .history/
389 |
390 | # Windows Installer files from build outputs
391 | *.cab
392 | *.msi
393 | *.msix
394 | *.msm
395 | *.msp
396 |
397 | # JetBrains Rider
398 | *.sln.iml
399 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
2 | WORKDIR /app
3 | EXPOSE 5000
4 |
5 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
6 | RUN pwd && ls /
7 | WORKDIR /src
8 | COPY ["src/WebApi/WebApi.csproj", "src/WebApi/"]
9 | RUN pwd && ls
10 | COPY ["src/Application/Application.csproj", "src/Application/"]
11 | COPY ["src/Domain/Domain.csproj", "src/Domain/"]
12 | COPY ["src/Infrastructure/Infrastructure.csproj", "src/Infrastructure/"]
13 | RUN dotnet restore "./src/WebApi/WebApi.csproj"
14 | COPY . .
15 | WORKDIR "/src/src/WebApi"
16 | RUN dotnet build "WebApi.csproj" -c Release -o /app/build
17 |
18 | FROM build AS publish
19 | RUN dotnet publish "WebApi.csproj" -c Release -o /app/publish
20 |
21 | FROM base AS final
22 | WORKDIR /app
23 |
24 | COPY --from=publish /app/publish .
25 | ENTRYPOINT ["dotnet", "WebApi.dll"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Sara Rasoulian
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## ASP.NETCore Web API Sample
2 |
3 | This repository is a sample ASP.NET Core Web API (.NET 7) project.
4 |
5 | ## Features & Technologies
6 | - ASP.NET Core Web API
7 | - Entity Framework Core
8 | - Clean Architecture
9 | - Unit Of Work Pattern
10 | - Repository Service Pattern
11 | - TDD
12 | - PostgreSQL
13 | - Docker
14 |
15 | ## Get started
16 |
17 | #### 1. Clone the repository
18 |
19 | ```
20 | git clone https://github.com/SaraRasoulian/WebAPI-Sample-DotNet-7.git
21 | ```
22 | #### 2. Start with docker compose
23 |
24 | Make sure [docker](https://docs.docker.com/get-docker/) is installed on your machine.
25 |
26 | Run the following command in project directory:
27 |
28 | ```
29 | docker-compose up -d
30 | ```
31 |
32 | Docker compose in this project includes 3 services: web API application, postgres and pgadmin4.
33 |
34 | - Web API application will be running and listening at `http://localhost:5000`
35 |
36 | - Postgres database will be listening at `http://localhost:5433`
37 |
38 | - PgAdmin4 web interface will be listening at `http://localhost:8080`
39 |
40 |
41 | To apply your modified code, you can add build option:
42 |
43 | ```
44 | docker-compose up -d --build
45 | ```
46 |
47 | To stop and remove all containers, use the following command:
48 |
49 | ```
50 | docker-compose down
51 | ```
52 |
53 |
54 | #### 3. Run the migrations
55 |
56 | Open Sample.sln file in visual studio, then in package manager console tab, run:
57 |
58 | ```
59 | update-database
60 | ```
61 |
62 | This command will generate the database schema in postgres container.
63 |
64 | ## Contributions
65 | Contributions are welcomed! If you identify areas for improvement, please feel free to raise an issue or submit a pull request.
66 |
--------------------------------------------------------------------------------
/Sample.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6ED356A7-8B47-4613-AD01-C85CF28491BD}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{664D406C-2F83-48F0-BFC3-408D5CB53C65}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E2DA20AA-28D1-455C-BF50-C49A8F831633}"
11 | ProjectSection(SolutionItems) = preProject
12 | .dockerignore = .dockerignore
13 | .gitignore = .gitignore
14 | docker-compose.yml = docker-compose.yml
15 | Dockerfile = Dockerfile
16 | README.md = README.md
17 | EndProjectSection
18 | EndProject
19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "src\Domain\Domain.csproj", "{9F5BE64F-7BA3-40D2-9DEF-14F534E84989}"
20 | EndProject
21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "src\Application\Application.csproj", "{5895FEF3-F864-43DC-AAD5-CFB5D4DFDA13}"
22 | EndProject
23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{FBBC90CE-43DC-423C-87B8-110C81C623C6}"
24 | EndProject
25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "src\WebApi\WebApi.csproj", "{E7A97BCC-0CC2-4947-9F43-646E5243C4CF}"
26 | EndProject
27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application.Tests", "tests\Application.Tests\Application.Tests.csproj", "{9B3DDC83-BC3A-4F39-811D-7A41D6291780}"
28 | EndProject
29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests", "tests\WebApi.Tests\WebApi.Tests.csproj", "{46C453F6-6E88-4A69-AC70-1E9416A7C870}"
30 | EndProject
31 | Global
32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
33 | Debug|Any CPU = Debug|Any CPU
34 | Release|Any CPU = Release|Any CPU
35 | EndGlobalSection
36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
37 | {9F5BE64F-7BA3-40D2-9DEF-14F534E84989}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {9F5BE64F-7BA3-40D2-9DEF-14F534E84989}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {9F5BE64F-7BA3-40D2-9DEF-14F534E84989}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {9F5BE64F-7BA3-40D2-9DEF-14F534E84989}.Release|Any CPU.Build.0 = Release|Any CPU
41 | {5895FEF3-F864-43DC-AAD5-CFB5D4DFDA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {5895FEF3-F864-43DC-AAD5-CFB5D4DFDA13}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {5895FEF3-F864-43DC-AAD5-CFB5D4DFDA13}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {5895FEF3-F864-43DC-AAD5-CFB5D4DFDA13}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {FBBC90CE-43DC-423C-87B8-110C81C623C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {FBBC90CE-43DC-423C-87B8-110C81C623C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {FBBC90CE-43DC-423C-87B8-110C81C623C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {FBBC90CE-43DC-423C-87B8-110C81C623C6}.Release|Any CPU.Build.0 = Release|Any CPU
49 | {E7A97BCC-0CC2-4947-9F43-646E5243C4CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {E7A97BCC-0CC2-4947-9F43-646E5243C4CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {E7A97BCC-0CC2-4947-9F43-646E5243C4CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {E7A97BCC-0CC2-4947-9F43-646E5243C4CF}.Release|Any CPU.Build.0 = Release|Any CPU
53 | {9B3DDC83-BC3A-4F39-811D-7A41D6291780}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
54 | {9B3DDC83-BC3A-4F39-811D-7A41D6291780}.Debug|Any CPU.Build.0 = Debug|Any CPU
55 | {9B3DDC83-BC3A-4F39-811D-7A41D6291780}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {9B3DDC83-BC3A-4F39-811D-7A41D6291780}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {46C453F6-6E88-4A69-AC70-1E9416A7C870}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {46C453F6-6E88-4A69-AC70-1E9416A7C870}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {46C453F6-6E88-4A69-AC70-1E9416A7C870}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {46C453F6-6E88-4A69-AC70-1E9416A7C870}.Release|Any CPU.Build.0 = Release|Any CPU
61 | EndGlobalSection
62 | GlobalSection(SolutionProperties) = preSolution
63 | HideSolutionNode = FALSE
64 | EndGlobalSection
65 | GlobalSection(NestedProjects) = preSolution
66 | {9F5BE64F-7BA3-40D2-9DEF-14F534E84989} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
67 | {5895FEF3-F864-43DC-AAD5-CFB5D4DFDA13} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
68 | {FBBC90CE-43DC-423C-87B8-110C81C623C6} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
69 | {E7A97BCC-0CC2-4947-9F43-646E5243C4CF} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
70 | {9B3DDC83-BC3A-4F39-811D-7A41D6291780} = {664D406C-2F83-48F0-BFC3-408D5CB53C65}
71 | {46C453F6-6E88-4A69-AC70-1E9416A7C870} = {664D406C-2F83-48F0-BFC3-408D5CB53C65}
72 | EndGlobalSection
73 | GlobalSection(ExtensibilityGlobals) = postSolution
74 | SolutionGuid = {3CB609D9-5D54-4C11-A371-DAAC8B74E430}
75 | EndGlobalSection
76 | EndGlobal
77 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | webapi:
3 | container_name: webapi_container
4 | ports:
5 | - "5000:5000"
6 | depends_on:
7 | - postgres_db
8 | build:
9 | context: .
10 | dockerfile: Dockerfile
11 | environment:
12 | - ConnectionStrings__DefaultConnection=User ID=sara;Password=mysecretpassword;Server=postgres_container;Port=5432;Database=SampleDB;IntegratedSecurity=true;Pooling=true;
13 | - ASPNETCORE_URLS=http://+:5000
14 | networks:
15 | - dev_network
16 | postgres_db:
17 | container_name: postgres_container
18 | image: postgres
19 | environment:
20 | POSTGRES_USER: sara
21 | POSTGRES_PASSWORD: mysecretpassword
22 | PGDATA: /data/postgres
23 | volumes:
24 | - postgres_data:/data/postgres
25 | ports:
26 | - "5433:5432"
27 | networks:
28 | - dev_network
29 | restart: unless-stopped
30 |
31 | pgadmin:
32 | container_name: pgadmin_container
33 | image: dpage/pgadmin4
34 | environment:
35 | PGADMIN_DEFAULT_EMAIL: admin@gmail.com
36 | PGADMIN_DEFAULT_PASSWORD: mysecretpassword
37 | PGADMIN_CONFIG_SERVER_MODE: 'False'
38 | volumes:
39 | - pgadmin_data:/var/lib/pgadmin
40 |
41 | ports:
42 | - "8080:80"
43 | networks:
44 | - dev_network
45 | restart: unless-stopped
46 |
47 | networks:
48 | dev_network:
49 | driver: bridge
50 |
51 | volumes:
52 | postgres_data:
53 | pgadmin_data:
--------------------------------------------------------------------------------
/src/Application/Application.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Application/Dtos/EducationDto.cs:
--------------------------------------------------------------------------------
1 | namespace Application.Dtos
2 | {
3 | public class EducationDto
4 | {
5 | public Guid Id { get; set; }
6 | public required string Degree { get; set; }
7 | public required string FieldOfStudy { get; set; }
8 | public required string School { get; set; }
9 | public string? Description { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Application/Service/EducationService.cs:
--------------------------------------------------------------------------------
1 | using Application.Dtos;
2 | using Application.Interfaces;
3 | using Application.Service.Interfaces;
4 | using Domain.Entities;
5 | using Mapster;
6 |
7 | namespace Application.Service
8 | {
9 | public class EducationService : IEducationService
10 | {
11 | private readonly IUnitOfWork _unitOfWork;
12 | public EducationService(IUnitOfWork unitOfWork)
13 | {
14 | _unitOfWork = unitOfWork;
15 | }
16 |
17 | public async Task> GetAll()
18 | {
19 | var result = await _unitOfWork.Education.GetAll();
20 | return result.Adapt>();
21 | }
22 |
23 | public async Task GetById(Guid id)
24 | {
25 | var result = await _unitOfWork.Education.GetById(id);
26 | return result?.Adapt();
27 | }
28 |
29 | public async Task Add(EducationDto model)
30 | {
31 | Education toAdd = model.Adapt();
32 | toAdd.Id = Guid.NewGuid();
33 | var result = await _unitOfWork.Education.Add(toAdd);
34 | await _unitOfWork.SaveChangesAsync();
35 | return result.Adapt();
36 | }
37 |
38 | public async Task Update(Guid id, EducationDto model)
39 | {
40 | if (model is null || model.Id != id) return false;
41 |
42 | var toUpdate = await _unitOfWork.Education.GetById(id);
43 | if (toUpdate is null) return false;
44 |
45 | toUpdate = model.Adapt();
46 |
47 | _unitOfWork.Education.Update(toUpdate);
48 | await _unitOfWork.SaveChangesAsync();
49 | return true;
50 | }
51 |
52 | public async Task Delete(Guid id)
53 | {
54 | var toDelete = await _unitOfWork.Education.GetById(id);
55 | if (toDelete is null) return false;
56 |
57 | _unitOfWork.Education.Delete(toDelete);
58 | await _unitOfWork.SaveChangesAsync();
59 | return true;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Application/Service/Interfaces/IEducationService.cs:
--------------------------------------------------------------------------------
1 | using Application.Dtos;
2 |
3 | namespace Application.Service.Interfaces
4 | {
5 | public interface IEducationService
6 | {
7 | Task> GetAll();
8 | Task GetById(Guid id);
9 | Task Add(EducationDto model);
10 | Task Update(Guid id, EducationDto model);
11 | Task Delete(Guid id);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Application/UoW/IUnitOfWork.cs:
--------------------------------------------------------------------------------
1 | using Domain.Interfaces;
2 |
3 | namespace Application.Interfaces
4 | {
5 | public interface IUnitOfWork : IDisposable
6 | {
7 | IEducationRepository Education { get; }
8 | Task SaveChangesAsync(CancellationToken cancellationToken = default);
9 | }
10 | }
--------------------------------------------------------------------------------
/src/Domain/Common/AuditableEntityBase.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Domain.Common
4 | {
5 | public abstract class AuditableEntityBase
6 | {
7 | [Key]
8 | public Guid Id { get; set; }
9 | public DateTime CreatedAt { get; set; }
10 | public Guid CreatedBy { get; set; }
11 | public DateTime LastUpdatedAt { get; set; }
12 | public Guid LastUpdatedBy { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Domain/Common/EntityBase.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Domain.Common
4 | {
5 | public abstract class EntityBase
6 | {
7 | [Key]
8 | public Guid Id { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Domain/Domain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/Domain/Entities/Education.cs:
--------------------------------------------------------------------------------
1 | using Domain.Common;
2 |
3 | namespace Domain.Entities
4 | {
5 | public class Education : EntityBase
6 | {
7 | public string? Degree { get; set; }
8 |
9 | public string? FieldOfStudy { get; set; }
10 |
11 | public string? School { get; set; }
12 |
13 | public string? Description { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Domain/Interfaces/IEducationRepository.cs:
--------------------------------------------------------------------------------
1 | using Domain.Entities;
2 | namespace Domain.Interfaces
3 | {
4 | public interface IEducationRepository
5 | {
6 | Task> GetAll();
7 | Task GetById(Guid id);
8 | Task Add(Education model);
9 | void Update(Education model);
10 | void Delete(Education model);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/Infrastructure/Configurations/EducationConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Domain.Entities;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
4 |
5 | namespace Infrastructure.Configurations;
6 |
7 | public class EducationConfiguration : IEntityTypeConfiguration
8 | {
9 | public void Configure(EntityTypeBuilder builder)
10 | {
11 | builder.Property(t => t.Degree)
12 | .HasMaxLength(50)
13 | .IsRequired();
14 |
15 | builder.Property(t => t.FieldOfStudy)
16 | .HasMaxLength(250)
17 | .IsRequired();
18 |
19 | builder.Property(t => t.School)
20 | .HasMaxLength(250)
21 | .IsRequired();
22 |
23 | builder.Property(t => t.Description)
24 | .HasMaxLength(1000);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Infrastructure/DbContexts/SampleDbContext.cs:
--------------------------------------------------------------------------------
1 | using Domain.Entities;
2 | using Microsoft.EntityFrameworkCore;
3 | using System.Reflection;
4 |
5 | namespace Infrastructure.DbContexts
6 | {
7 | public class SampleDbContext : DbContext
8 | {
9 | public SampleDbContext(DbContextOptions options) : base(options) { }
10 |
11 | public DbSet Educations { get; set; }
12 |
13 | protected override void OnModelCreating(ModelBuilder modelBuilder)
14 | {
15 | modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
16 |
17 | // Seed
18 | modelBuilder.Entity().HasData(
19 | new Education
20 | {
21 | Id = Guid.NewGuid(),
22 | Degree = "Bachelor's degree",
23 | FieldOfStudy = "Software engineering",
24 | School = "Sample university"
25 | }
26 | );
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Infrastructure/Infrastructure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 | all
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 |
15 |
16 |
17 | all
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/Infrastructure/Migrations/20240113141226_Initialize.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Infrastructure.DbContexts;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
9 |
10 | #nullable disable
11 |
12 | namespace Infrastructure.Migrations
13 | {
14 | [DbContext(typeof(SampleDbContext))]
15 | [Migration("20240113141226_Initialize")]
16 | partial class Initialize
17 | {
18 | ///
19 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
20 | {
21 | #pragma warning disable 612, 618
22 | modelBuilder
23 | .HasAnnotation("ProductVersion", "7.0.10")
24 | .HasAnnotation("Relational:MaxIdentifierLength", 63);
25 |
26 | NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
27 |
28 | modelBuilder.Entity("Domain.Entities.Education", b =>
29 | {
30 | b.Property("Id")
31 | .ValueGeneratedOnAdd()
32 | .HasColumnType("uuid");
33 |
34 | b.Property("Degree")
35 | .IsRequired()
36 | .HasMaxLength(50)
37 | .HasColumnType("character varying(50)");
38 |
39 | b.Property("Description")
40 | .HasMaxLength(1000)
41 | .HasColumnType("character varying(1000)");
42 |
43 | b.Property("FieldOfStudy")
44 | .IsRequired()
45 | .HasMaxLength(250)
46 | .HasColumnType("character varying(250)");
47 |
48 | b.Property("School")
49 | .IsRequired()
50 | .HasMaxLength(250)
51 | .HasColumnType("character varying(250)");
52 |
53 | b.HasKey("Id");
54 |
55 | b.ToTable("Educations");
56 |
57 | b.HasData(
58 | new
59 | {
60 | Id = new Guid("c92ea179-dd5c-46ca-b7b5-b44a191b974c"),
61 | Degree = "Bachelor's degree",
62 | FieldOfStudy = "Software engineering",
63 | School = "Sample university"
64 | });
65 | });
66 | #pragma warning restore 612, 618
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Infrastructure/Migrations/20240113141226_Initialize.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | #nullable disable
5 |
6 | namespace Infrastructure.Migrations
7 | {
8 | ///
9 | public partial class Initialize : Migration
10 | {
11 | ///
12 | protected override void Up(MigrationBuilder migrationBuilder)
13 | {
14 | migrationBuilder.CreateTable(
15 | name: "Educations",
16 | columns: table => new
17 | {
18 | Id = table.Column(type: "uuid", nullable: false),
19 | Degree = table.Column(type: "character varying(50)", maxLength: 50, nullable: false),
20 | FieldOfStudy = table.Column(type: "character varying(250)", maxLength: 250, nullable: false),
21 | School = table.Column(type: "character varying(250)", maxLength: 250, nullable: false),
22 | Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true)
23 | },
24 | constraints: table =>
25 | {
26 | table.PrimaryKey("PK_Educations", x => x.Id);
27 | });
28 |
29 | migrationBuilder.InsertData(
30 | table: "Educations",
31 | columns: new[] { "Id", "Degree", "Description", "FieldOfStudy", "School" },
32 | values: new object[] { new Guid("c92ea179-dd5c-46ca-b7b5-b44a191b974c"), "Bachelor's degree", null, "Software engineering", "Sample university" });
33 | }
34 |
35 | ///
36 | protected override void Down(MigrationBuilder migrationBuilder)
37 | {
38 | migrationBuilder.DropTable(
39 | name: "Educations");
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Infrastructure/Migrations/SampleDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Infrastructure.DbContexts;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
7 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
8 |
9 | #nullable disable
10 |
11 | namespace Infrastructure.Migrations
12 | {
13 | [DbContext(typeof(SampleDbContext))]
14 | partial class SampleDbContextModelSnapshot : ModelSnapshot
15 | {
16 | protected override void BuildModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "7.0.10")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 63);
22 |
23 | NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
24 |
25 | modelBuilder.Entity("Domain.Entities.Education", b =>
26 | {
27 | b.Property("Id")
28 | .ValueGeneratedOnAdd()
29 | .HasColumnType("uuid");
30 |
31 | b.Property("Degree")
32 | .IsRequired()
33 | .HasMaxLength(50)
34 | .HasColumnType("character varying(50)");
35 |
36 | b.Property("Description")
37 | .HasMaxLength(1000)
38 | .HasColumnType("character varying(1000)");
39 |
40 | b.Property("FieldOfStudy")
41 | .IsRequired()
42 | .HasMaxLength(250)
43 | .HasColumnType("character varying(250)");
44 |
45 | b.Property("School")
46 | .IsRequired()
47 | .HasMaxLength(250)
48 | .HasColumnType("character varying(250)");
49 |
50 | b.HasKey("Id");
51 |
52 | b.ToTable("Educations");
53 |
54 | b.HasData(
55 | new
56 | {
57 | Id = new Guid("c92ea179-dd5c-46ca-b7b5-b44a191b974c"),
58 | Degree = "Bachelor's degree",
59 | FieldOfStudy = "Software engineering",
60 | School = "Sample university"
61 | });
62 | });
63 | #pragma warning restore 612, 618
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Infrastructure/Repositories/EducationRepository.cs:
--------------------------------------------------------------------------------
1 | using Domain.Entities;
2 | using Domain.Interfaces;
3 | using Infrastructure.DbContexts;
4 | using Microsoft.EntityFrameworkCore;
5 |
6 | namespace Infrastructure.Repositories
7 | {
8 | public class EducationRepository : IEducationRepository
9 | {
10 | private readonly SampleDbContext _dbContext;
11 | public EducationRepository(SampleDbContext dbContext)
12 | {
13 | _dbContext = dbContext;
14 | }
15 |
16 | public async Task> GetAll()
17 | {
18 | return await _dbContext.Educations.ToListAsync();
19 | }
20 |
21 | public async Task GetById(Guid id)
22 | {
23 | return await _dbContext.Educations.AsNoTracking().FirstOrDefaultAsync(c=>c.Id == id);
24 | }
25 |
26 | public async Task Add(Education model)
27 | {
28 | var result = await _dbContext.Educations.AddAsync(model);
29 | return result.Entity;
30 | }
31 |
32 | public void Update(Education model)
33 | {
34 | _dbContext.Educations.Update(model);
35 | }
36 |
37 | public void Delete(Education model)
38 | {
39 | _dbContext.Educations.Remove(model);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Infrastructure/UoW/UnitOfWork.cs:
--------------------------------------------------------------------------------
1 | using Application.Interfaces;
2 | using Domain.Interfaces;
3 | using Infrastructure.DbContexts;
4 | using Infrastructure.Repositories;
5 |
6 | namespace Infrastructure.UoW
7 | {
8 | public sealed class UnitOfWork : IUnitOfWork
9 | {
10 | public IEducationRepository Education { get; private set; }
11 |
12 | private readonly SampleDbContext _dbContext;
13 | public UnitOfWork(SampleDbContext dbContext)
14 | {
15 | _dbContext = dbContext;
16 | Education = new EducationRepository(_dbContext);
17 | }
18 |
19 | public async Task SaveChangesAsync(CancellationToken cancellationToken = default)
20 | {
21 | return await _dbContext.SaveChangesAsync(cancellationToken);
22 | }
23 |
24 | public void Dispose()
25 | {
26 | _dbContext.DisposeAsync();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/WebApi/Controllers/EducationsController.cs:
--------------------------------------------------------------------------------
1 | using Application.Dtos;
2 | using Application.Service.Interfaces;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace WebApi.Controllers
6 | {
7 | [Route("api/[controller]")]
8 | [ApiController]
9 | public class EducationsController : ControllerBase
10 | {
11 | private readonly IEducationService _educationService;
12 | public EducationsController(IEducationService educationService)
13 | {
14 | _educationService = educationService;
15 | }
16 |
17 | [HttpGet]
18 | public async Task Get()
19 | {
20 | try
21 | {
22 | var result = await _educationService.GetAll();
23 | return Ok(result);
24 | }
25 | catch (Exception)
26 | {
27 | return StatusCode(StatusCodes.Status500InternalServerError);
28 | }
29 | }
30 |
31 | [HttpGet("{id:guid}")]
32 | public async Task Get([FromRoute] Guid id)
33 | {
34 | try
35 | {
36 | var result = await _educationService.GetById(id);
37 | if (result is null) return NoContent();
38 | return Ok(result);
39 | }
40 | catch (Exception)
41 | {
42 | return StatusCode(StatusCodes.Status500InternalServerError);
43 | }
44 | }
45 |
46 | [HttpPost]
47 | public async Task Post([FromBody] EducationDto model)
48 | {
49 | try
50 | {
51 | if (!ModelState.IsValid || model is null) return BadRequest(ModelState);
52 | var result = await _educationService.Add(model);
53 | return Ok(result);
54 | }
55 | catch (Exception)
56 | {
57 | return StatusCode(StatusCodes.Status500InternalServerError);
58 | }
59 | }
60 |
61 | [HttpPut("{id:guid}")]
62 | public async Task Put([FromRoute] Guid id, [FromBody] EducationDto model)
63 | {
64 | try
65 | {
66 | if (!ModelState.IsValid) return BadRequest(ModelState);
67 | var result = await _educationService.Update(id, model);
68 | if (!result) return BadRequest();
69 | return Ok();
70 | }
71 | catch (Exception)
72 | {
73 | return StatusCode(StatusCodes.Status500InternalServerError);
74 | }
75 | }
76 |
77 | [HttpDelete("{id:guid}")]
78 | public async Task Delete([FromRoute] Guid id)
79 | {
80 | try
81 | {
82 | var result = await _educationService.Delete(id);
83 | if (!result) return BadRequest();
84 | return Ok();
85 | }
86 | catch (Exception)
87 | {
88 | return StatusCode(StatusCodes.Status500InternalServerError);
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/WebApi/Program.cs:
--------------------------------------------------------------------------------
1 | using Application.Interfaces;
2 | using Application.Service.Interfaces;
3 | using Application.Service;
4 | using Infrastructure.DbContexts;
5 | using Infrastructure.UoW;
6 | using Microsoft.EntityFrameworkCore;
7 |
8 | var builder = WebApplication.CreateBuilder(args);
9 |
10 | // Add services to the container.
11 |
12 | builder.Services.AddControllers();
13 |
14 | // Database
15 | builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"), o => o.UseNodaTime()));
16 | builder.Services.AddHealthChecks().AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection"), name: "SampleDB");
17 |
18 | builder.Services.AddScoped();
19 | builder.Services.AddScoped();
20 |
21 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
22 | builder.Services.AddEndpointsApiExplorer();
23 | builder.Services.AddSwaggerGen();
24 |
25 | var app = builder.Build();
26 |
27 | // Configure the HTTP request pipeline.
28 | if (app.Environment.IsDevelopment())
29 | {
30 | app.UseSwagger();
31 | app.UseSwaggerUI();
32 | }
33 |
34 | app.UseHttpsRedirection();
35 |
36 | app.UseAuthorization();
37 |
38 | app.MapControllers();
39 |
40 | app.Run();
41 |
--------------------------------------------------------------------------------
/src/WebApi/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:63868",
8 | "sslPort": 44301
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "launchUrl": "swagger",
17 | "applicationUrl": "http://localhost:5033",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "https": {
23 | "commandName": "Project",
24 | "dotnetRunMessages": true,
25 | "launchBrowser": true,
26 | "launchUrl": "swagger",
27 | "applicationUrl": "https://localhost:7110;http://localhost:5033",
28 | "environmentVariables": {
29 | "ASPNETCORE_ENVIRONMENT": "Development"
30 | }
31 | },
32 | "IIS Express": {
33 | "commandName": "IISExpress",
34 | "launchBrowser": true,
35 | "launchUrl": "swagger",
36 | "environmentVariables": {
37 | "ASPNETCORE_ENVIRONMENT": "Development"
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/WebApi/WebApi.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 | all
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/WebApi/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/WebApi/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "DefaultConnection": "Host=localhost;Port=5433;Database=SampleDB;Username=sara;Password=mysecretpassword"
4 | },
5 | "Logging": {
6 | "LogLevel": {
7 | "Default": "Information",
8 | "Microsoft.AspNetCore": "Warning"
9 | }
10 | },
11 | "AllowedHosts": "*"
12 | }
13 |
--------------------------------------------------------------------------------
/tests/Application.Tests/Application.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 | all
19 |
20 |
21 | runtime; build; native; contentfiles; analyzers; buildtransitive
22 | all
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/Application.Tests/EducationServiceTests.cs:
--------------------------------------------------------------------------------
1 | using Application.Dtos;
2 | using Application.Interfaces;
3 | using Application.Service;
4 | using Domain.Entities;
5 | using Domain.Interfaces;
6 | using FakeItEasy;
7 | using Mapster;
8 |
9 | namespace Application.Tests
10 | {
11 | public class EducationServiceTests
12 | {
13 | private readonly IUnitOfWork unitOfWork;
14 | private readonly IEducationRepository educationRepository;
15 |
16 | public EducationServiceTests()
17 | {
18 | unitOfWork = A.Fake();
19 | educationRepository = A.Fake();
20 | A.CallTo(() => unitOfWork.Education).Returns(educationRepository);
21 | }
22 |
23 | [Fact]
24 | public async Task GetAll_With_Data_Returns_List_Of_EducationDto()
25 | {
26 | // Arrange
27 | var educationData = new List();
28 | A.CallTo(() => educationRepository.GetAll()).Returns(educationData);
29 | var educationService = new EducationService(unitOfWork);
30 |
31 | // Act
32 | var result = await educationService.GetAll();
33 |
34 | // Assert
35 | Assert.NotNull(result);
36 | Assert.IsAssignableFrom>(result);
37 | Assert.Equal(educationData.Count, result.Count());
38 | }
39 |
40 | [Fact]
41 | public async Task GetById_With_Data_Returns_EducationDto()
42 | {
43 | // Arrange
44 | var educationId = Guid.NewGuid();
45 | var educationData = A.Dummy();
46 | A.CallTo(() => educationRepository.GetById(educationId)).Returns(educationData);
47 | var educationService = new EducationService(unitOfWork);
48 |
49 | // Act
50 | var result = await educationService.GetById(educationId);
51 |
52 | // Assert
53 | Assert.NotNull(result);
54 | Assert.IsType(result);
55 | }
56 |
57 | [Fact]
58 | public async Task Add_Returns_Added_EducationDto()
59 | {
60 | // Arrange
61 | var educationDto = A.Dummy();
62 | var addedEducation = educationDto.Adapt();
63 | addedEducation.Id = Guid.NewGuid();
64 | A.CallTo(() => educationRepository.Add(A._)).Returns(addedEducation);
65 |
66 | var educationService = new EducationService(unitOfWork);
67 |
68 | // Act
69 | var result = await educationService.Add(educationDto);
70 |
71 | // Assert
72 | Assert.NotNull(result);
73 | Assert.IsType(result);
74 | Assert.Equal(addedEducation.Id, result.Id);
75 | }
76 |
77 | [Fact]
78 | public async Task Update_For_Successful_Update_Returns_True()
79 | {
80 | // Arrange
81 | var existingId = Guid.NewGuid();
82 | var existingEducation = A.Dummy();
83 | existingEducation.Id = existingId;
84 | A.CallTo(() => educationRepository.GetById(existingId)).Returns(existingEducation);
85 |
86 | var updatedEducationDto = A.Dummy();
87 | updatedEducationDto.Id = existingId;
88 | var educationService = new EducationService(unitOfWork);
89 |
90 | // Act
91 | var result = await educationService.Update(existingId, updatedEducationDto);
92 |
93 | // Assert
94 | Assert.True(result);
95 | A.CallTo(() => unitOfWork.Education.Update(A.That.Matches(e => e.Id == existingId))).MustHaveHappenedOnceExactly();
96 | A.CallTo(() => unitOfWork.SaveChangesAsync(default)).MustHaveHappenedOnceExactly();
97 | }
98 |
99 | [Fact]
100 | public async Task Update_For_Id_Mismatch_Returns_False()
101 | {
102 | // Arrange
103 | var educationService = new EducationService(unitOfWork);
104 | var invalidEducationDto = A.Dummy();
105 | var existingId = Guid.NewGuid();
106 |
107 | // Act
108 | var result = await educationService.Update(existingId, invalidEducationDto);
109 |
110 | // Assert
111 | Assert.False(result);
112 | A.CallTo(() => unitOfWork.Education.GetById(A._)).MustNotHaveHappened();
113 | A.CallTo(() => unitOfWork.Education.Update(A._)).MustNotHaveHappened();
114 | A.CallTo(() => unitOfWork.SaveChangesAsync(default)).MustNotHaveHappened();
115 | }
116 |
117 | [Fact]
118 | public async Task Delete_With_Existing_Id_Returns_True()
119 | {
120 | // Arrange
121 | var existingId = Guid.NewGuid();
122 | var existingEducation = A.Dummy();
123 | A.CallTo(() => educationRepository.GetById(existingId)).Returns(existingEducation);
124 |
125 | var educationService = new EducationService(unitOfWork);
126 |
127 | // Act
128 | var result = await educationService.Delete(existingId);
129 |
130 | // Assert
131 | Assert.True(result);
132 | A.CallTo(() => unitOfWork.Education.Delete(existingEducation)).MustHaveHappenedOnceExactly();
133 | A.CallTo(() => unitOfWork.SaveChangesAsync(default)).MustHaveHappenedOnceExactly();
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/tests/Application.Tests/Usings.cs:
--------------------------------------------------------------------------------
1 | global using Xunit;
--------------------------------------------------------------------------------
/tests/WebApi.Tests/EducationsControllerTests.cs:
--------------------------------------------------------------------------------
1 | using Application.Dtos;
2 | using Application.Service.Interfaces;
3 | using FakeItEasy;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.AspNetCore.Mvc;
6 | using WebApi.Controllers;
7 |
8 | namespace WebApi.Tests
9 | {
10 | public class EducationsControllerTests
11 | {
12 | private readonly IEducationService educationService;
13 | public EducationsControllerTests()
14 | {
15 | educationService = A.Fake();
16 | }
17 |
18 | #region Get
19 | [Fact]
20 | public async Task Get_With_Data_Returns_Ok()
21 | {
22 | // Arrange
23 | var dummyEducationData = A.CollectionOfDummy(3);
24 | A.CallTo(() => educationService.GetAll()).Returns(dummyEducationData);
25 | var controller = new EducationsController(educationService);
26 |
27 | // Act
28 | var result = await controller.Get();
29 |
30 | // Assert
31 | var okResult = Assert.IsType(result);
32 | var data = Assert.IsType>(okResult.Value);
33 | Assert.Equal(3, data.Count);
34 | }
35 |
36 | [Fact]
37 | public async Task Get_On_Exception_Returns_InternalServerError()
38 | {
39 | // Arrange
40 | A.CallTo(() => educationService.GetAll()).Throws(new Exception());
41 | var controller = new EducationsController(educationService);
42 |
43 | // Act
44 | var result = await controller.Get();
45 |
46 | // Assert
47 | var statusCodeResult = Assert.IsType(result);
48 | Assert.Equal(500, statusCodeResult.StatusCode);
49 | }
50 |
51 | [Fact]
52 | public async Task GetById_With_Valid_Id_Returns_Ok()
53 | {
54 | // Arrange
55 | var expectedDto = A.Dummy();
56 | var validId = expectedDto.Id;
57 | A.CallTo(() => educationService.GetById(validId)).Returns(expectedDto);
58 | var controller = new EducationsController(educationService);
59 |
60 | // Act
61 | var result = await controller.Get(validId);
62 |
63 | // Assert
64 | var okResult = Assert.IsType(result);
65 | }
66 |
67 | [Fact]
68 | public async Task GetById_On_Exception_Returns_InternalServerError()
69 | {
70 | // Arrange
71 | var id = Guid.NewGuid();
72 | A.CallTo(() => educationService.GetById(id)).Throws(new Exception());
73 | var controller = new EducationsController(educationService);
74 |
75 | // Act
76 | var result = await controller.Get(id);
77 |
78 | // Assert
79 | var statusCodeResult = Assert.IsType(result);
80 | Assert.Equal(StatusCodes.Status500InternalServerError, statusCodeResult.StatusCode);
81 | }
82 | #endregion
83 |
84 | #region Post
85 | [Fact]
86 | public async Task Post_With_Valid_Model_Returns_Ok()
87 | {
88 | // Arrange
89 | var validEducationDto = A.Dummy();
90 | A.CallTo(() => educationService.Add(validEducationDto)).Returns(validEducationDto);
91 | var controller = new EducationsController(educationService);
92 |
93 | // Act
94 | var result = await controller.Post(validEducationDto);
95 |
96 | // Assert
97 | Assert.IsType(result);
98 | }
99 |
100 | [Fact]
101 | public async Task Post_With_Null_Input_Returns_BadRequest()
102 | {
103 | // Arrange
104 | var controller = new EducationsController(educationService);
105 |
106 | // Act
107 | var result = await controller.Post(null);
108 |
109 | // Assert
110 | Assert.IsType(result);
111 | }
112 |
113 | [Fact]
114 | public async Task Post_On_Exception_Returns_InternalServerError()
115 | {
116 | // Arrange
117 | A.CallTo(() => educationService.Add(A._)).Throws(new Exception());
118 | var controller = new EducationsController(educationService);
119 | var validEducationDto = A.Dummy();
120 |
121 | // Act
122 | var result = await controller.Post(validEducationDto);
123 |
124 | // Assert
125 | var statusCodeResult = Assert.IsType(result);
126 | Assert.Equal(StatusCodes.Status500InternalServerError, statusCodeResult.StatusCode);
127 | }
128 | #endregion
129 |
130 | #region Put
131 | [Fact]
132 | public async Task Put_For_Successful_Update_Returns_Ok()
133 | {
134 | // Arrange
135 | A.CallTo(() => educationService.Update(A._, A._)).Returns(true);
136 | var controller = new EducationsController(educationService);
137 | var existingId = Guid.NewGuid();
138 | var educationDto = A.Dummy();
139 |
140 | // Act
141 | var result = await controller.Put(existingId, educationDto);
142 |
143 | // Assert
144 | Assert.IsType(result);
145 | }
146 |
147 | [Fact]
148 | public async Task Put_For_Failed_Update_Returns_BadRequest()
149 | {
150 | // Arrange
151 | A.CallTo(() => educationService.Update(A._, A._)).Returns(false);
152 | var controller = new EducationsController(educationService);
153 | var existingId = Guid.NewGuid();
154 | var educationDto = A.Dummy();
155 |
156 | // Act
157 | var result = await controller.Put(existingId, educationDto);
158 |
159 | // Assert
160 | Assert.IsType(result);
161 | }
162 |
163 | [Fact]
164 | public async Task Put_With_Null_Input_Returns_BadRequest()
165 | {
166 | // Arrange
167 | var controller = new EducationsController(educationService);
168 |
169 | // Act
170 | var result = await controller.Put(Guid.NewGuid(), null);
171 |
172 | // Assert
173 | Assert.IsType(result);
174 | }
175 |
176 | [Fact]
177 | public async Task Put_On_Exception_Returns_InternalServerError()
178 | {
179 | // Arrange
180 | A.CallTo(() => educationService.Update(A._, A._)).Throws(new Exception());
181 | var controller = new EducationsController(educationService);
182 |
183 | // Act
184 | var result = await controller.Put(Guid.NewGuid(), A.Dummy());
185 |
186 | // Assert
187 | var statusCodeResult = Assert.IsType(result);
188 | Assert.Equal(StatusCodes.Status500InternalServerError, statusCodeResult.StatusCode);
189 | }
190 | #endregion
191 |
192 | #region Delete
193 | [Fact]
194 | public async Task Delete_For_Successful_Deletion_Returns_Ok()
195 | {
196 | // Arrange
197 | A.CallTo(() => educationService.Delete(A._)).Returns(true);
198 | var controller = new EducationsController(educationService);
199 |
200 | // Act
201 | var result = await controller.Delete(Guid.NewGuid());
202 |
203 | // Assert
204 | Assert.IsType(result);
205 | }
206 |
207 | [Fact]
208 | public async Task Delete_For_Failed_Deletion_Returns_BadRequest()
209 | {
210 | // Arrange
211 | A.CallTo(() => educationService.Delete(A._)).Returns(false);
212 | var controller = new EducationsController(educationService);
213 |
214 | // Act
215 | var result = await controller.Delete(Guid.NewGuid());
216 |
217 | // Assert
218 | Assert.IsType(result);
219 | }
220 |
221 | [Fact]
222 | public async Task Delete_On_Exception_Returns_InternalServerError()
223 | {
224 | // Arrange
225 | A.CallTo(() => educationService.Delete(A._)).Throws(new Exception());
226 | var controller = new EducationsController(educationService);
227 |
228 | // Act
229 | var result = await controller.Delete(Guid.NewGuid());
230 |
231 | // Assert
232 | var statusCodeResult = Assert.IsType(result);
233 | Assert.Equal(StatusCodes.Status500InternalServerError, statusCodeResult.StatusCode);
234 | }
235 | #endregion
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/tests/WebApi.Tests/Usings.cs:
--------------------------------------------------------------------------------
1 | global using Xunit;
--------------------------------------------------------------------------------
/tests/WebApi.Tests/WebApi.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 | all
19 |
20 |
21 | runtime; build; native; contentfiles; analyzers; buildtransitive
22 | all
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------