├── .gitignore ├── Clean.API.Project.sln ├── LICENSE ├── Project.API ├── Controllers │ ├── CustomerController.cs │ ├── OrderController.cs │ └── ProductController.cs ├── Extensions │ ├── OrderControllerBenchmark.cs │ └── ServiceExtension.cs ├── Middlewares │ └── RequestResponseLoggingMiddleware.cs ├── Program.cs ├── Project.API.csproj ├── Properties │ └── launchSettings.json ├── appsettings.Development.json └── appsettings.json ├── Project.Core ├── Entities │ ├── Business │ │ ├── CustomerViewModel.cs │ │ ├── OrderDetailsViewModel.cs │ │ ├── OrderViewModel.cs │ │ ├── PaginatedDataViewModel.cs │ │ └── ProductViewModel.cs │ └── General │ │ ├── Base.cs │ │ ├── Customer.cs │ │ ├── Order.cs │ │ ├── OrderDetails.cs │ │ └── Product.cs ├── Exceptions │ └── NotFoundException.cs ├── Interfaces │ ├── IMapper │ │ └── IBaseMapper.cs │ ├── IRepositories │ │ ├── IBaseRepository.cs │ │ ├── ICustomerRepository.cs │ │ ├── IOrderDetailsRepository.cs │ │ ├── IOrderRepository.cs │ │ └── IProductRepository.cs │ └── IServices │ │ ├── ICustomerService.cs │ │ ├── IOrderService.cs │ │ └── IProductService.cs ├── Mapper │ └── BaseMapper.cs ├── Project.Core.csproj └── Services │ ├── CustomerService.cs │ ├── OrderService.cs │ └── ProductService.cs ├── Project.Infrastructure ├── Data │ ├── ApplicationDbContext.cs │ └── ApplicationDbContextConfigurations.cs ├── Migrations │ ├── 20230918095000_Initial_Migration.Designer.cs │ ├── 20230918095000_Initial_Migration.cs │ └── ApplicationDbContextModelSnapshot.cs ├── Project.Infrastructure.csproj └── Repositories │ ├── BaseRepository.cs │ ├── CustomerRepository.cs │ ├── OrderDetailsRepository.cs │ ├── OrderRepository.cs │ └── ProductRepository.cs ├── Project.UnitTest ├── API │ └── ProductControllerTests.cs ├── Core │ └── ProductServiceTests.cs ├── Infrastructure │ └── ProductRepositoryTests.cs ├── Project.UnitTest.csproj └── Usings.cs └── README.md /.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 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # Tye 66 | .tye/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.tlog 97 | *.vspscc 98 | *.vssscc 99 | .builds 100 | *.pidb 101 | *.svclog 102 | *.scc 103 | 104 | # Chutzpah Test files 105 | _Chutzpah* 106 | 107 | # Visual C++ cache files 108 | ipch/ 109 | *.aps 110 | *.ncb 111 | *.opendb 112 | *.opensdf 113 | *.sdf 114 | *.cachefile 115 | *.VC.db 116 | *.VC.VC.opendb 117 | 118 | # Visual Studio profiler 119 | *.psess 120 | *.vsp 121 | *.vspx 122 | *.sap 123 | 124 | # Visual Studio Trace Files 125 | *.e2e 126 | 127 | # TFS 2012 Local Workspace 128 | $tf/ 129 | 130 | # Guidance Automation Toolkit 131 | *.gpState 132 | 133 | # ReSharper is a .NET coding add-in 134 | _ReSharper*/ 135 | *.[Rr]e[Ss]harper 136 | *.DotSettings.user 137 | 138 | # TeamCity is a build add-in 139 | _TeamCity* 140 | 141 | # DotCover is a Code Coverage Tool 142 | *.dotCover 143 | 144 | # AxoCover is a Code Coverage Tool 145 | .axoCover/* 146 | !.axoCover/settings.json 147 | 148 | # Coverlet is a free, cross platform Code Coverage Tool 149 | coverage*.json 150 | coverage*.xml 151 | coverage*.info 152 | 153 | # Visual Studio code coverage results 154 | *.coverage 155 | *.coveragexml 156 | 157 | # NCrunch 158 | _NCrunch_* 159 | .*crunch*.local.xml 160 | nCrunchTemp_* 161 | 162 | # MightyMoose 163 | *.mm.* 164 | AutoTest.Net/ 165 | 166 | # Web workbench (sass) 167 | .sass-cache/ 168 | 169 | # Installshield output folder 170 | [Ee]xpress/ 171 | 172 | # DocProject is a documentation generator add-in 173 | DocProject/buildhelp/ 174 | DocProject/Help/*.HxT 175 | DocProject/Help/*.HxC 176 | DocProject/Help/*.hhc 177 | DocProject/Help/*.hhk 178 | DocProject/Help/*.hhp 179 | DocProject/Help/Html2 180 | DocProject/Help/html 181 | 182 | # Click-Once directory 183 | publish/ 184 | 185 | # Publish Web Output 186 | *.[Pp]ublish.xml 187 | *.azurePubxml 188 | # Note: Comment the next line if you want to checkin your web deploy settings, 189 | # but database connection strings (with potential passwords) will be unencrypted 190 | *.pubxml 191 | *.publishproj 192 | 193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 194 | # checkin your Azure Web App publish settings, but sensitive information contained 195 | # in these scripts will be unencrypted 196 | PublishScripts/ 197 | 198 | # NuGet Packages 199 | *.nupkg 200 | # NuGet Symbol Packages 201 | *.snupkg 202 | # The packages folder can be ignored because of Package Restore 203 | **/[Pp]ackages/* 204 | # except build/, which is used as an MSBuild target. 205 | !**/[Pp]ackages/build/ 206 | # Uncomment if necessary however generally it will be regenerated when needed 207 | #!**/[Pp]ackages/repositories.config 208 | # NuGet v3's project.json files produces more ignorable files 209 | *.nuget.props 210 | *.nuget.targets 211 | 212 | # Microsoft Azure Build Output 213 | csx/ 214 | *.build.csdef 215 | 216 | # Microsoft Azure Emulator 217 | ecf/ 218 | rcf/ 219 | 220 | # Windows Store app package directories and files 221 | AppPackages/ 222 | BundleArtifacts/ 223 | Package.StoreAssociation.xml 224 | _pkginfo.txt 225 | *.appx 226 | *.appxbundle 227 | *.appxupload 228 | 229 | # Visual Studio cache files 230 | # files ending in .cache can be ignored 231 | *.[Cc]ache 232 | # but keep track of directories ending in .cache 233 | !?*.[Cc]ache/ 234 | 235 | # Others 236 | ClientBin/ 237 | ~$* 238 | *~ 239 | *.dbmdl 240 | *.dbproj.schemaview 241 | *.jfm 242 | *.pfx 243 | *.publishsettings 244 | orleans.codegen.cs 245 | 246 | # Including strong name files can present a security risk 247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 248 | #*.snk 249 | 250 | # Since there are multiple workflows, uncomment next line to ignore bower_components 251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 252 | #bower_components/ 253 | 254 | # RIA/Silverlight projects 255 | Generated_Code/ 256 | 257 | # Backup & report files from converting an old project file 258 | # to a newer Visual Studio version. Backup files are not needed, 259 | # because we have git ;-) 260 | _UpgradeReport_Files/ 261 | Backup*/ 262 | UpgradeLog*.XML 263 | UpgradeLog*.htm 264 | ServiceFabricBackup/ 265 | *.rptproj.bak 266 | 267 | # SQL Server files 268 | *.mdf 269 | *.ldf 270 | *.ndf 271 | 272 | # Business Intelligence projects 273 | *.rdl.data 274 | *.bim.layout 275 | *.bim_*.settings 276 | *.rptproj.rsuser 277 | *- [Bb]ackup.rdl 278 | *- [Bb]ackup ([0-9]).rdl 279 | *- [Bb]ackup ([0-9][0-9]).rdl 280 | 281 | # Microsoft Fakes 282 | FakesAssemblies/ 283 | 284 | # GhostDoc plugin setting file 285 | *.GhostDoc.xml 286 | 287 | # Node.js Tools for Visual Studio 288 | .ntvs_analysis.dat 289 | node_modules/ 290 | 291 | # Visual Studio 6 build log 292 | *.plg 293 | 294 | # Visual Studio 6 workspace options file 295 | *.opt 296 | 297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 298 | *.vbw 299 | 300 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 301 | *.vbp 302 | 303 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 304 | *.dsw 305 | *.dsp 306 | 307 | # Visual Studio 6 technical files 308 | *.ncb 309 | *.aps 310 | 311 | # Visual Studio LightSwitch build output 312 | **/*.HTMLClient/GeneratedArtifacts 313 | **/*.DesktopClient/GeneratedArtifacts 314 | **/*.DesktopClient/ModelManifest.xml 315 | **/*.Server/GeneratedArtifacts 316 | **/*.Server/ModelManifest.xml 317 | _Pvt_Extensions 318 | 319 | # Paket dependency manager 320 | .paket/paket.exe 321 | paket-files/ 322 | 323 | # FAKE - F# Make 324 | .fake/ 325 | 326 | # CodeRush personal settings 327 | .cr/personal 328 | 329 | # Python Tools for Visual Studio (PTVS) 330 | __pycache__/ 331 | *.pyc 332 | 333 | # Cake - Uncomment if you are using it 334 | # tools/** 335 | # !tools/packages.config 336 | 337 | # Tabs Studio 338 | *.tss 339 | 340 | # Telerik's JustMock configuration file 341 | *.jmconfig 342 | 343 | # BizTalk build output 344 | *.btp.cs 345 | *.btm.cs 346 | *.odx.cs 347 | *.xsd.cs 348 | 349 | # OpenCover UI analysis results 350 | OpenCover/ 351 | 352 | # Azure Stream Analytics local run output 353 | ASALocalRun/ 354 | 355 | # MSBuild Binary and Structured Log 356 | *.binlog 357 | 358 | # NVidia Nsight GPU debugger configuration file 359 | *.nvuser 360 | 361 | # MFractors (Xamarin productivity tool) working folder 362 | .mfractor/ 363 | 364 | # Local History for Visual Studio 365 | .localhistory/ 366 | 367 | # Visual Studio History (VSHistory) files 368 | .vshistory/ 369 | 370 | # BeatPulse healthcheck temp database 371 | healthchecksdb 372 | 373 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 374 | MigrationBackup/ 375 | 376 | # Ionide (cross platform F# VS Code tools) working folder 377 | .ionide/ 378 | 379 | # Fody - auto-generated XML schema 380 | FodyWeavers.xsd 381 | 382 | # VS Code files for those working on multiple tools 383 | .vscode/* 384 | !.vscode/settings.json 385 | !.vscode/tasks.json 386 | !.vscode/launch.json 387 | !.vscode/extensions.json 388 | *.code-workspace 389 | 390 | # Local History for Visual Studio Code 391 | .history/ 392 | 393 | # Windows Installer files from build outputs 394 | *.cab 395 | *.msi 396 | *.msix 397 | *.msm 398 | *.msp 399 | 400 | # JetBrains Rider 401 | *.sln.iml 402 | 403 | ## 404 | ## Visual studio for Mac 405 | ## 406 | 407 | 408 | # globs 409 | Makefile.in 410 | *.userprefs 411 | *.usertasks 412 | config.make 413 | config.status 414 | aclocal.m4 415 | install-sh 416 | autom4te.cache/ 417 | *.tar.gz 418 | tarballs/ 419 | test-results/ 420 | 421 | # Mac bundle stuff 422 | *.dmg 423 | *.app 424 | 425 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 426 | # General 427 | .DS_Store 428 | .AppleDouble 429 | .LSOverride 430 | 431 | # Icon must end with two \r 432 | Icon 433 | 434 | 435 | # Thumbnails 436 | ._* 437 | 438 | # Files that might appear in the root of a volume 439 | .DocumentRevisions-V100 440 | .fseventsd 441 | .Spotlight-V100 442 | .TemporaryItems 443 | .Trashes 444 | .VolumeIcon.icns 445 | .com.apple.timemachine.donotpresent 446 | 447 | # Directories potentially created on remote AFP share 448 | .AppleDB 449 | .AppleDesktop 450 | Network Trash Folder 451 | Temporary Items 452 | .apdisk 453 | 454 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 455 | # Windows thumbnail cache files 456 | Thumbs.db 457 | ehthumbs.db 458 | ehthumbs_vista.db 459 | 460 | # Dump file 461 | *.stackdump 462 | 463 | # Folder config file 464 | [Dd]esktop.ini 465 | 466 | # Recycle Bin used on file shares 467 | $RECYCLE.BIN/ 468 | 469 | # Windows Installer files 470 | *.cab 471 | *.msi 472 | *.msix 473 | *.msm 474 | *.msp 475 | 476 | # Windows shortcuts 477 | *.lnk 478 | -------------------------------------------------------------------------------- /Clean.API.Project.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33712.159 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project.Core", "Project.Core\Project.Core.csproj", "{540CC202-D687-4B54-8746-4E943F0D16C6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project.Infrastructure", "Project.Infrastructure\Project.Infrastructure.csproj", "{A9767277-DD3A-47BA-9704-E525D32F1D9D}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project.UnitTest", "Project.UnitTest\Project.UnitTest.csproj", "{9A138398-C199-404D-9E42-3095A2290A07}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project.API", "Project.API\Project.API.csproj", "{E80FE11C-4A4B-456B-A79A-9201F5A11060}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {540CC202-D687-4B54-8746-4E943F0D16C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {540CC202-D687-4B54-8746-4E943F0D16C6}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {540CC202-D687-4B54-8746-4E943F0D16C6}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {540CC202-D687-4B54-8746-4E943F0D16C6}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {A9767277-DD3A-47BA-9704-E525D32F1D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {A9767277-DD3A-47BA-9704-E525D32F1D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {A9767277-DD3A-47BA-9704-E525D32F1D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {A9767277-DD3A-47BA-9704-E525D32F1D9D}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {9A138398-C199-404D-9E42-3095A2290A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {9A138398-C199-404D-9E42-3095A2290A07}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {9A138398-C199-404D-9E42-3095A2290A07}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {9A138398-C199-404D-9E42-3095A2290A07}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {E80FE11C-4A4B-456B-A79A-9201F5A11060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {E80FE11C-4A4B-456B-A79A-9201F5A11060}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {E80FE11C-4A4B-456B-A79A-9201F5A11060}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {E80FE11C-4A4B-456B-A79A-9201F5A11060}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {554B3771-ADE1-4A3F-8808-562B64E046E0} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Kawser Hamid 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 | -------------------------------------------------------------------------------- /Project.API/Controllers/CustomerController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Project.Core.Entities.Business; 3 | using Project.Core.Interfaces.IServices; 4 | 5 | namespace Project.API.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class CustomerController : ControllerBase 10 | { 11 | private readonly ILogger _logger; 12 | private readonly ICustomerService _customerService; 13 | 14 | public CustomerController(ILogger logger, ICustomerService customerService) 15 | { 16 | _logger = logger; 17 | _customerService = customerService; 18 | } 19 | 20 | 21 | // GET: api/customer/paginated 22 | [HttpGet("paginated")] 23 | public async Task Get(int? pageNumber, int? pageSize) 24 | { 25 | try 26 | { 27 | int pageSizeValue = (pageSize ?? 4); 28 | int pageNumberValue = (pageNumber ?? 1); 29 | 30 | //Get peginated data 31 | var customers = await _customerService.GetPaginatedCustomers(pageNumberValue, pageSizeValue); 32 | 33 | return Ok(customers); 34 | } 35 | catch (Exception ex) 36 | { 37 | _logger.LogError(ex, "An error occurred while retrieving customers"); 38 | return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); 39 | } 40 | } 41 | 42 | 43 | // GET: api/customer 44 | [HttpGet] 45 | public async Task Get() 46 | { 47 | try 48 | { 49 | var customers = await _customerService.GetCustomers(); 50 | return Ok(customers); 51 | } 52 | catch (Exception ex) 53 | { 54 | _logger.LogError(ex, "An error occurred while retrieving customers"); 55 | return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); 56 | } 57 | 58 | } 59 | 60 | 61 | // GET api/customer/5 62 | [HttpGet("{id}")] 63 | public async Task Get(int id) 64 | { 65 | try 66 | { 67 | var data = await _customerService.GetCustomer(id); 68 | return Ok(data); 69 | } 70 | catch (Exception ex) 71 | { 72 | if (ex.Message == "No data found") 73 | { 74 | return StatusCode(StatusCodes.Status404NotFound, ex.Message); 75 | } 76 | _logger.LogError(ex, $"An error occurred while retrieving the customer"); 77 | return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); 78 | } 79 | } 80 | 81 | 82 | // POST api/customer 83 | [HttpPost] 84 | public async Task Create(CustomerViewModel model) 85 | { 86 | if (ModelState.IsValid) 87 | { 88 | string message = ""; 89 | if (await _customerService.IsExists("Email", model.Email)) 90 | { 91 | message = $"The customer email- '{model.Email}' already exists"; 92 | return StatusCode(StatusCodes.Status400BadRequest, message); 93 | } 94 | 95 | try 96 | { 97 | var data = await _customerService.Create(model); 98 | return Ok(data); 99 | } 100 | catch (Exception ex) 101 | { 102 | _logger.LogError(ex, $"An error occurred while adding the customer"); 103 | message = $"An error occurred while adding the customer- {ex.Message}"; 104 | 105 | return StatusCode(StatusCodes.Status500InternalServerError, message); 106 | } 107 | } 108 | return StatusCode(StatusCodes.Status400BadRequest, "Please input all required data"); 109 | } 110 | 111 | // PUT api/customer/5 112 | [HttpPut] 113 | public async Task Edit(CustomerViewModel model) 114 | { 115 | if (ModelState.IsValid) 116 | { 117 | string message = ""; 118 | if (await _customerService.IsExistsForUpdate(model.Id, "Email", model.Email)) 119 | { 120 | message = $"The customer email- '{model.Email}' already exists"; 121 | return StatusCode(StatusCodes.Status400BadRequest, message); 122 | } 123 | 124 | try 125 | { 126 | await _customerService.Update(model); 127 | return Ok(); 128 | } 129 | catch (Exception ex) 130 | { 131 | _logger.LogError(ex, $"An error occurred while updating the customer"); 132 | message = $"An error occurred while updating the customer- {ex.Message}"; 133 | 134 | return StatusCode(StatusCodes.Status500InternalServerError, message); 135 | } 136 | } 137 | return StatusCode(StatusCodes.Status400BadRequest, "Please input all required data"); 138 | } 139 | 140 | 141 | // DELETE api/customer/5 142 | [HttpDelete("{id}")] 143 | public async Task Delete(int id) 144 | { 145 | try 146 | { 147 | await _customerService.Delete(id); 148 | return Ok(); 149 | } 150 | catch (Exception ex) 151 | { 152 | _logger.LogError(ex, "An error occurred while deleting the customer"); 153 | return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while deleting the customer- " + ex.Message); 154 | } 155 | } 156 | 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Project.API/Controllers/OrderController.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Running; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Project.Core.Entities.Business; 5 | using Project.Core.Interfaces.IServices; 6 | 7 | namespace Project.API.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | [MemoryDiagnoser] 12 | public class OrderController : ControllerBase 13 | { 14 | private readonly ILogger _logger; 15 | private readonly IOrderService _orderService; 16 | 17 | public OrderController(ILogger logger, IOrderService orderService) 18 | { 19 | _logger = logger; 20 | _orderService = orderService; 21 | } 22 | 23 | 24 | // GET: api/order/paginated 25 | [HttpGet("paginated")] 26 | public async Task Get(int? pageNumber, int? pageSize) 27 | { 28 | try 29 | { 30 | int pageSizeValue = (pageSize ?? 4); 31 | int pageNumberValue = (pageNumber ?? 1); 32 | 33 | //Get peginated data 34 | var orders = await _orderService.GetPaginatedOrders(pageNumberValue, pageSizeValue); 35 | return Ok(orders); 36 | } 37 | catch (Exception ex) 38 | { 39 | _logger.LogError(ex, "An error occurred while retrieving orders"); 40 | return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); 41 | } 42 | } 43 | 44 | 45 | // GET: api/order 46 | [HttpGet] 47 | public async Task Get() 48 | { 49 | try 50 | { 51 | var orders = await _orderService.GetOrders(); 52 | return Ok(orders); 53 | } 54 | catch (Exception ex) 55 | { 56 | _logger.LogError(ex, "An error occurred while retrieving orders"); 57 | return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); 58 | } 59 | 60 | } 61 | 62 | // GET: api/order/5 63 | [HttpGet("{orderId}")] 64 | public async Task Get(int orderId) 65 | { 66 | try 67 | { 68 | var data = await _orderService.GetOrder(orderId); 69 | return Ok(data); 70 | } 71 | catch (Exception ex) 72 | { 73 | if (ex.Message == "No data found") 74 | { 75 | return StatusCode(StatusCodes.Status404NotFound, ex.Message); 76 | } 77 | _logger.LogError(ex, $"An error occurred while retrieving the order"); 78 | return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); 79 | } 80 | } 81 | 82 | 83 | // POST api/order 84 | [HttpPost] 85 | public async Task Create(OrderViewModel model) 86 | { 87 | if (ModelState.IsValid) 88 | { 89 | string message = ""; 90 | 91 | try 92 | { 93 | var data = await _orderService.Create(model); 94 | return Ok(data); 95 | } 96 | catch (Exception ex) 97 | { 98 | _logger.LogError(ex, $"An error occurred while adding the order"); 99 | message = $"An error occurred while adding the order- {ex.Message}"; 100 | 101 | return StatusCode(StatusCodes.Status500InternalServerError, message); 102 | } 103 | } 104 | return StatusCode(StatusCodes.Status400BadRequest, "Please input all required data"); 105 | } 106 | 107 | // PUT api/order/5 108 | [HttpPut] 109 | public async Task Edit(OrderViewModel model) 110 | { 111 | if (ModelState.IsValid) 112 | { 113 | string message = ""; 114 | 115 | try 116 | { 117 | await _orderService.Update(model); 118 | return Ok(); 119 | } 120 | catch (Exception ex) 121 | { 122 | _logger.LogError(ex, $"An error occurred while updating the order"); 123 | message = $"An error occurred while updating the order- {ex.Message}"; 124 | 125 | return StatusCode(StatusCodes.Status500InternalServerError, message); 126 | } 127 | } 128 | return StatusCode(StatusCodes.Status400BadRequest, "Please input all required data"); 129 | } 130 | 131 | 132 | // DELETE api/order/5 133 | [HttpDelete("{id}")] 134 | public async Task Delete(int id) 135 | { 136 | try 137 | { 138 | await _orderService.Delete(id); 139 | return Ok(); 140 | } 141 | catch (Exception ex) 142 | { 143 | _logger.LogError(ex, "An error occurred while deleting the order"); 144 | return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while deleting the order- " + ex.Message); 145 | } 146 | } 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Project.API/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Project.Core.Entities.Business; 3 | using Project.Core.Interfaces.IServices; 4 | 5 | namespace Project.API.Controllers 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class ProductController : ControllerBase 10 | { 11 | private readonly ILogger _logger; 12 | private readonly IProductService _productService; 13 | 14 | public ProductController(ILogger logger, IProductService productService) 15 | { 16 | _logger = logger; 17 | _productService = productService; 18 | } 19 | 20 | 21 | // GET: api/product/paginated 22 | [HttpGet("paginated")] 23 | public async Task Get(int? pageNumber, int? pageSize) 24 | { 25 | try 26 | { 27 | int pageSizeValue = (pageSize ?? 4); 28 | int pageNumberValue = (pageNumber ?? 1); 29 | 30 | //Get peginated data 31 | var products = await _productService.GetPaginatedProducts(pageNumberValue, pageSizeValue); 32 | 33 | return Ok(products); 34 | } 35 | catch (Exception ex) 36 | { 37 | _logger.LogError(ex, "An error occurred while retrieving products"); 38 | return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); 39 | } 40 | } 41 | 42 | 43 | // GET: api/product 44 | [HttpGet] 45 | public async Task Get() 46 | { 47 | try 48 | { 49 | var products = await _productService.GetProducts(); 50 | return Ok(products); 51 | } 52 | catch (Exception ex) 53 | { 54 | _logger.LogError(ex, "An error occurred while retrieving products"); 55 | return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); 56 | } 57 | 58 | } 59 | 60 | 61 | // GET api/product/5 62 | [HttpGet("{id}")] 63 | public async Task Get(int id) 64 | { 65 | try 66 | { 67 | var data = await _productService.GetProduct(id); 68 | return Ok(data); 69 | } 70 | catch (Exception ex) 71 | { 72 | if (ex.Message == "No data found") 73 | { 74 | return StatusCode(StatusCodes.Status404NotFound, ex.Message); 75 | } 76 | _logger.LogError(ex, $"An error occurred while retrieving the product"); 77 | return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); 78 | } 79 | } 80 | 81 | 82 | // POST api/product 83 | [HttpPost] 84 | public async Task Create(ProductViewModel model) 85 | { 86 | if (ModelState.IsValid) 87 | { 88 | string message = ""; 89 | if (await _productService.IsExists("Name", model.Name)) 90 | { 91 | message = $"The product name- '{model.Name}' already exists"; 92 | return StatusCode(StatusCodes.Status400BadRequest, message); 93 | } 94 | 95 | if (await _productService.IsExists("Code", model.Code)) 96 | { 97 | message = $"The product code- '{model.Code}' already exists"; 98 | return StatusCode(StatusCodes.Status400BadRequest, message); 99 | } 100 | 101 | try 102 | { 103 | var data = await _productService.Create(model); 104 | return Ok(data); 105 | } 106 | catch (Exception ex) 107 | { 108 | _logger.LogError(ex, $"An error occurred while adding the product"); 109 | message = $"An error occurred while adding the product- " + ex.Message; 110 | 111 | return StatusCode(StatusCodes.Status500InternalServerError, message); 112 | } 113 | } 114 | return StatusCode(StatusCodes.Status400BadRequest, "Please input all required data"); 115 | } 116 | 117 | // PUT api/product/5 118 | [HttpPut] 119 | public async Task Edit(ProductViewModel model) 120 | { 121 | if (ModelState.IsValid) 122 | { 123 | string message = ""; 124 | if (await _productService.IsExistsForUpdate(model.Id, "Name", model.Name)) 125 | { 126 | message = "The product name- '{model.Name}' already exists"; 127 | return StatusCode(StatusCodes.Status400BadRequest, message); 128 | } 129 | 130 | if (await _productService.IsExistsForUpdate(model.Id, "Code", model.Code)) 131 | { 132 | message = $"The product code- '{model.Code}' already exists"; 133 | return StatusCode(StatusCodes.Status400BadRequest, message); 134 | } 135 | 136 | try 137 | { 138 | await _productService.Update(model); 139 | return Ok(); 140 | } 141 | catch (Exception ex) 142 | { 143 | _logger.LogError(ex, $"An error occurred while updating the product"); 144 | message = $"An error occurred while updating the product- " + ex.Message; 145 | 146 | return StatusCode(StatusCodes.Status500InternalServerError, message); 147 | } 148 | } 149 | return StatusCode(StatusCodes.Status400BadRequest, "Please input all required data"); 150 | } 151 | 152 | 153 | // DELETE api/product/5 154 | [HttpDelete("{id}")] 155 | public async Task Delete(int id) 156 | { 157 | try 158 | { 159 | await _productService.Delete(id); 160 | return Ok(); 161 | } 162 | catch (Exception ex) 163 | { 164 | _logger.LogError(ex, "An error occurred while deleting the product"); 165 | return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while deleting the product- " + ex.Message); 166 | } 167 | } 168 | 169 | [HttpGet("PriceCheck/{productId}")] 170 | public async Task PriceCheck(int productId) 171 | { 172 | try 173 | { 174 | var price = await _productService.PriceCheck(productId); 175 | return Ok(price); 176 | } 177 | catch (Exception ex) 178 | { 179 | _logger.LogError(ex, "An error occurred while checking product price"); 180 | return StatusCode(StatusCodes.Status500InternalServerError, $"An error occurred while checking product price- {ex.Message}"); 181 | } 182 | } 183 | 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Project.API/Extensions/OrderControllerBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Moq; 4 | using Project.API.Controllers; 5 | using Project.Core.Interfaces.IServices; 6 | 7 | namespace Project.API.Extensions 8 | { 9 | public class OrderControllerBenchmark 10 | { 11 | private readonly OrderController _orderController; 12 | 13 | public OrderControllerBenchmark() 14 | { 15 | // Initialize any dependencies needed by the controller here 16 | // For example, you might need to create a mock logger and service 17 | // to pass to the controller's constructor. 18 | var logger = new Mock>().Object; 19 | var orderService = new Mock().Object; 20 | 21 | _orderController = new OrderController(logger, orderService); 22 | } 23 | 24 | [Benchmark] 25 | public async Task GetOrderById() 26 | { 27 | return await _orderController.Get(7); 28 | } 29 | 30 | [Benchmark] 31 | public async Task GetOrderList() 32 | { 33 | return await _orderController.Get(); 34 | } 35 | 36 | [Benchmark] 37 | public async Task GetPaginatedOrderList() 38 | { 39 | return await _orderController.Get(2, 50); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Project.API/Extensions/ServiceExtension.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Project.Core.Entities.Business; 4 | using Project.Core.Entities.General; 5 | using Project.Core.Interfaces.IMapper; 6 | using Project.Core.Interfaces.IRepositories; 7 | using Project.Core.Interfaces.IServices; 8 | using Project.Core.Mapper; 9 | using Project.Core.Services; 10 | using Project.Infrastructure.Repositories; 11 | 12 | namespace Project.API.Extensions 13 | { 14 | public static class ServiceExtension 15 | { 16 | public static IServiceCollection RegisterService(this IServiceCollection services) 17 | { 18 | #region Services 19 | services.AddScoped(); 20 | services.AddScoped(); 21 | services.AddScoped(); 22 | 23 | #endregion 24 | 25 | #region Repositories 26 | services.AddTransient(); 27 | services.AddTransient(); 28 | services.AddTransient(); 29 | services.AddTransient(); 30 | 31 | #endregion 32 | 33 | #region Mapper 34 | var configuration = new MapperConfiguration(cfg => 35 | { 36 | cfg.CreateMap(); 37 | cfg.CreateMap(); 38 | 39 | cfg.CreateMap(); 40 | cfg.CreateMap(); 41 | 42 | cfg.CreateMap(); 43 | cfg.CreateMap(); 44 | }); 45 | 46 | IMapper mapper = configuration.CreateMapper(); 47 | 48 | // Register the IMapperService implementation with your dependency injection container 49 | services.AddSingleton>(new BaseMapper(mapper)); 50 | services.AddSingleton>(new BaseMapper(mapper)); 51 | 52 | services.AddSingleton>(new BaseMapper(mapper)); 53 | services.AddSingleton>(new BaseMapper(mapper)); 54 | 55 | services.AddSingleton>(new BaseMapper(mapper)); 56 | services.AddSingleton>(new BaseMapper(mapper)); 57 | 58 | #endregion 59 | 60 | return services; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Project.API/Middlewares/RequestResponseLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Project.API.Middlewares 4 | { 5 | public class RequestResponseLoggingMiddleware 6 | { 7 | private readonly RequestDelegate _next; 8 | private readonly ILogger _logger; 9 | 10 | public RequestResponseLoggingMiddleware(RequestDelegate next, ILogger logger) 11 | { 12 | _next = next; 13 | _logger = logger; 14 | } 15 | 16 | public async Task Invoke(HttpContext context) 17 | { 18 | // Log the incoming request 19 | LogRequest(context.Request); 20 | 21 | 22 | // Call the next middleware in the pipeline 23 | await _next(context); 24 | 25 | // Log the outgoing response 26 | LogResponse(context.Response); 27 | } 28 | 29 | private void LogRequest(HttpRequest request) 30 | { 31 | _logger.LogInformation($"Request received: {request.Method} {request.Path}"); 32 | _logger.LogInformation($"Request headers: {GetHeadersAsString(request.Headers)}"); 33 | } 34 | 35 | private void LogResponse(HttpResponse response) 36 | { 37 | _logger.LogInformation($"Response sent: {response.StatusCode}"); 38 | _logger.LogInformation($"Response headers: {GetHeadersAsString(response.Headers)}"); 39 | } 40 | 41 | private string GetHeadersAsString(IHeaderDictionary headers) 42 | { 43 | var stringBuilder = new StringBuilder(); 44 | foreach (var (key, value) in headers) 45 | { 46 | stringBuilder.AppendLine($"{key}: {value}"); 47 | } 48 | return stringBuilder.ToString(); 49 | } 50 | } 51 | 52 | // Extension method used to add the middleware to the HTTP request pipeline. 53 | public static class RequestResponseLoggingMiddlewareExtensions 54 | { 55 | public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder) 56 | { 57 | return builder.UseMiddleware(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Project.API/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Project.API.Middlewares; 3 | using Project.Infrastructure.Data; 4 | using Project.API.Extensions; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Logging; 7 | using BenchmarkDotNet.Running; 8 | using Project.API.Controllers; 9 | using BenchmarkDotNet.Configs; 10 | 11 | var builder = WebApplication.CreateBuilder(args); 12 | 13 | builder.Services.AddDbContext( 14 | options => options.UseSqlServer(builder.Configuration 15 | .GetConnectionString("PrimaryDbConnection"))); 16 | 17 | // Register ILogger service 18 | builder.Services.AddLogging(loggingBuilder => 19 | { 20 | loggingBuilder.AddSeq(builder.Configuration.GetSection("Seq")); 21 | }); 22 | 23 | //Register Services 24 | builder.Services.RegisterService(); 25 | builder.Services.AddControllers(); 26 | 27 | builder.Services.AddEndpointsApiExplorer(); 28 | builder.Services.AddSwaggerGen(options => 29 | { 30 | options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "Clean Structured API Project", Version = "v1" }); 31 | }); 32 | 33 | var app = builder.Build(); 34 | 35 | 36 | if (app.Environment.IsDevelopment()) 37 | { 38 | app.UseSwagger(); 39 | app.UseSwaggerUI(); 40 | } 41 | 42 | app.UseRouting(); // Add this line to configure routing 43 | 44 | app.UseEndpoints(endpoints => 45 | { 46 | endpoints.MapControllers(); // Map your regular API controllers 47 | 48 | // Add a custom endpoint for triggering benchmarks 49 | endpoints.MapGet("/runbenchmarks", async context => 50 | { 51 | // You can run the benchmarks here 52 | var summary = BenchmarkRunner.Run(); 53 | 54 | await context.Response.WriteAsync("Benchmarks completed. Check console for results."); 55 | }); 56 | }); 57 | 58 | #region Custom Middleware 59 | 60 | app.UseRequestResponseLogging(); 61 | #endregion 62 | 63 | 64 | 65 | app.Run(); 66 | -------------------------------------------------------------------------------- /Project.API/Project.API.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 | 26 | 27 | -------------------------------------------------------------------------------- /Project.API/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:13562", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5267", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Project.API/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Project.API/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "ConnectionStrings": { 9 | "PrimaryDbConnection": "server=YourServer; database=YourDatabase; Trusted_Connection=True;TrustServerCertificate=True" 10 | }, 11 | "Seq": { 12 | "ServerUrl": "http://localhost:5341", 13 | "ApiKey": "TLfE5FGnfmGjXatgs6xI", 14 | "MinimumLevel": "Trace", 15 | "LevelOverride": { 16 | "Microsoft": "Warning" 17 | } 18 | }, 19 | "AllowedHosts": "*" 20 | } 21 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/CustomerViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Entities.Business 10 | { 11 | public class CustomerViewModel 12 | { 13 | public int Id { get; set; } 14 | [Required, StringLength(maximumLength: 100, MinimumLength = 2)] 15 | public string FullName { get; set; } = string.Empty; 16 | [Required, DataType(DataType.EmailAddress), StringLength(maximumLength: 100, MinimumLength = 5)] 17 | public string Email { get; set; } = string.Empty; 18 | public decimal? Balance { get; set; } = 0; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/OrderDetailsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Entities.Business 10 | { 11 | public class OrderDetailsViewModel 12 | { 13 | public int OrderId { get; set; } 14 | [Required] 15 | public int ProductId { get; set; } 16 | public string? ProductName { get; set; } 17 | [Required] 18 | public decimal SellingPrice { get; set; } 19 | [Required] 20 | public int Quantity { get; set; } 21 | [StringLength(maximumLength: 350)] 22 | public string? Description { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/OrderViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Entities.Business 10 | { 11 | public class OrderViewModel 12 | { 13 | public int Id { get; set; } 14 | [Required] 15 | public int CustomerId { get; set; } 16 | public string? CustomerName { get; set; } 17 | public decimal TotalBill { get; set; } 18 | public int TotalQuantity { get; set; } 19 | public DateTime ProcessingData { get; set; } 20 | [StringLength(maximumLength: 350)] 21 | public string? Description { get; set; } 22 | 23 | public List? OrderDetails { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/PaginatedDataViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Project.Core.Entities.Business 8 | { 9 | public class PaginatedDataViewModel 10 | { 11 | public IEnumerable Data { get; set; } 12 | public int TotalCount { get; set; } 13 | 14 | public PaginatedDataViewModel(IEnumerable data, int totalCount) 15 | { 16 | Data = data; 17 | TotalCount = totalCount; 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/ProductViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Project.Core.Entities.Business 9 | { 10 | public class ProductViewModel 11 | { 12 | public int Id { get; set; } 13 | [Required, StringLength(maximumLength: 8, MinimumLength = 2)] 14 | public string Code { get; set; } = string.Empty; 15 | [Required, StringLength(maximumLength: 100, MinimumLength = 2)] 16 | public string Name { get; set; } = string.Empty; 17 | [Required, Range(0.01, float.MaxValue)] 18 | public double Price { get; set; } 19 | public int Quantity { get; set; } 20 | [StringLength(maximumLength: 350)] 21 | public string? Description { get; set; } 22 | public bool IsActive { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Project.Core/Entities/General/Base.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Project.Core.Entities.General 9 | { 10 | //Base class for entities common properties 11 | public class Base 12 | { 13 | [Key] 14 | public T Id { get; set; } 15 | public DateTime? EntryDate { get; set; } 16 | public DateTime? UpdateDate { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Project.Core/Entities/General/Customer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Entities.General 10 | { 11 | //Customer Table added for correlational things sharing 12 | [Table("Customers")] 13 | public class Customer : Base 14 | { 15 | [Required, StringLength(maximumLength: 100, MinimumLength = 2)] 16 | public string FullName { get; set; } = string.Empty; 17 | [Required, DataType(DataType.EmailAddress), StringLength(maximumLength: 100, MinimumLength = 5)] 18 | public string Email { get; set; } = string.Empty; 19 | public decimal? Balance { get; set; } = 0; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Project.Core/Entities/General/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Entities.General 10 | { 11 | [Table("Orders")] 12 | public class Order : Base 13 | { 14 | [Required] 15 | public int CustomerId { get; set; } 16 | [Required] 17 | public decimal TotalBill { get; set; } 18 | [Required] 19 | public int TotalQuantity { get; set; } 20 | [Required] 21 | public DateTime ProcessingData { get; set; } 22 | [StringLength(maximumLength: 350)] 23 | public string? Description { get; set; } 24 | 25 | [ForeignKey(nameof(CustomerId))] 26 | public virtual Customer Customer { get; set; } 27 | 28 | public virtual ICollection OrderDetails { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Project.Core/Entities/General/OrderDetails.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Entities.General 10 | { 11 | [Table("OrderDetails")] 12 | public class OrderDetails : Base 13 | { 14 | [Required] 15 | public int OrderId { get; set; } 16 | [Required] 17 | public int ProductId { get; set; } 18 | [Required] 19 | public decimal SellingPrice { get; set; } 20 | [Required] 21 | public int Quantity { get; set; } 22 | [StringLength(maximumLength: 350)] 23 | public string? Description { get; set; } 24 | 25 | [ForeignKey(nameof(ProductId))] 26 | public virtual Product Product { get; set; } 27 | [ForeignKey(nameof(OrderId))] 28 | public virtual Order Order { get; set; } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Project.Core/Entities/General/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Entities.General 10 | { 11 | [Table("Products")] 12 | public class Product : Base 13 | { 14 | [Required, StringLength(maximumLength: 8, MinimumLength = 2)] 15 | public string? Code { get; set; } 16 | [Required, StringLength(maximumLength: 100, MinimumLength = 2)] 17 | public string? Name { get; set; } 18 | [Required] 19 | public double Price { get; set; } 20 | public int Quantity { get; set; } 21 | [StringLength(maximumLength: 350)] 22 | public string? Description { get; set; } 23 | public bool IsActive { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Project.Core/Exceptions/NotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Project.Core.Exceptions 9 | { 10 | public class NotFoundException : Exception 11 | { 12 | public NotFoundException() 13 | { 14 | } 15 | 16 | public NotFoundException(string? message) : base(message) 17 | { 18 | } 19 | 20 | public NotFoundException(string? message, Exception? innerException) : base(message, innerException) 21 | { 22 | } 23 | 24 | protected NotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) 25 | { 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IMapper/IBaseMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Project.Core.Interfaces.IMapper 8 | { 9 | public interface IBaseMapper 10 | { 11 | TDestination MapModel(TSource source); 12 | IEnumerable MapList(IEnumerable source); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/IBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Project.Core.Interfaces.IRepositories 11 | { 12 | //Unit of Work Pattern 13 | public interface IBaseRepository where T : class 14 | { 15 | Task> GetAll(); 16 | Task> GetPaginatedData(int pageNumber, int pageSize); 17 | Task GetById(Tid id); 18 | Task IsExists(string key, Tvalue value); 19 | Task IsExistsForUpdate(Tid id, string key, string value); 20 | Task Create(T model); 21 | Task CreateRange(List model); 22 | Task Update(T model); 23 | Task Delete(T model); 24 | Task SaveChangeAsync(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/ICustomerRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Project.Core.Interfaces.IRepositories 9 | { 10 | public interface ICustomerRepository : IBaseRepository 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/IOrderDetailsRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Project.Core.Interfaces.IRepositories 9 | { 10 | public interface IOrderDetailsRepository : IBaseRepository 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/IOrderRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Interfaces.IRepositories 10 | { 11 | public interface IOrderRepository : IBaseRepository 12 | { 13 | Task> GetPaginatedData(int pageNumber, int pageSize); 14 | Task GetOrderById(int id); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/IProductRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Project.Core.Interfaces.IRepositories 9 | { 10 | public interface IProductRepository : IBaseRepository 11 | { 12 | Task PriceCheck(int productId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IServices/ICustomerService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Interfaces.IServices 10 | { 11 | public interface ICustomerService 12 | { 13 | Task> GetCustomers(); 14 | Task> GetPaginatedCustomers(int pageNumber, int pageSize); 15 | Task GetCustomer(int id); 16 | Task IsExists(string key, string value); 17 | Task IsExistsForUpdate(int id, string key, string value); 18 | Task Create(CustomerViewModel model); 19 | Task Update(CustomerViewModel model); 20 | Task Delete(int id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IServices/IOrderService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Interfaces.IServices 10 | { 11 | public interface IOrderService 12 | { 13 | Task> GetOrders(); 14 | Task> GetPaginatedOrders(int pageNumber, int pageSize); 15 | Task GetOrder(int id); 16 | Task IsExists(string key, string value); 17 | Task IsExistsForUpdate(int id, string key, string value); 18 | Task Create(OrderViewModel model); 19 | Task Update(OrderViewModel model); 20 | Task Delete(int id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IServices/IProductService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Interfaces.IServices 10 | { 11 | public interface IProductService 12 | { 13 | Task> GetProducts(); 14 | Task> GetPaginatedProducts(int pageNumber, int pageSize); 15 | Task GetProduct(int id); 16 | Task IsExists(string key, string value); 17 | Task IsExistsForUpdate(int id, string key, string value); 18 | Task Create(ProductViewModel model); 19 | Task Update(ProductViewModel model); 20 | Task Delete(int id); 21 | Task PriceCheck(int productId); 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Project.Core/Mapper/BaseMapper.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Project.Core.Interfaces.IMapper; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Project.Core.Mapper 10 | { 11 | public class BaseMapper : IBaseMapper 12 | { 13 | private readonly IMapper _mapper; 14 | 15 | public BaseMapper(IMapper mapper) 16 | { 17 | _mapper = mapper; 18 | } 19 | 20 | public TDestination MapModel(TSource source) 21 | { 22 | return _mapper.Map(source); 23 | } 24 | 25 | public IEnumerable MapList(IEnumerable source) 26 | { 27 | return _mapper.Map>(source); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Project.Core/Project.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Project.Core/Services/CustomerService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Exceptions; 4 | using Project.Core.Interfaces.IMapper; 5 | using Project.Core.Interfaces.IRepositories; 6 | using Project.Core.Interfaces.IServices; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace Project.Core.Services 14 | { 15 | public class CustomerService : ICustomerService 16 | { 17 | private readonly IBaseMapper _customerViewModelMapper; 18 | private readonly IBaseMapper _customerMapper; 19 | private readonly ICustomerRepository _customerRepository; 20 | 21 | public CustomerService( 22 | IBaseMapper customerViewModelMapper, 23 | IBaseMapper customerMapper, 24 | ICustomerRepository customerRepository) 25 | { 26 | _customerMapper = customerMapper; 27 | _customerViewModelMapper = customerViewModelMapper; 28 | _customerRepository = customerRepository; 29 | } 30 | 31 | public async Task> GetCustomers() 32 | { 33 | return _customerViewModelMapper.MapList(await _customerRepository.GetAll()); 34 | } 35 | 36 | public async Task> GetPaginatedCustomers(int pageNumber, int pageSize) 37 | { 38 | //Get peginated data 39 | var paginatedData = await _customerRepository.GetPaginatedData(pageNumber, pageSize); 40 | 41 | //Map data with ViewModel 42 | var mappedData = _customerViewModelMapper.MapList(paginatedData.Data); 43 | 44 | var paginatedDataViewModel = new PaginatedDataViewModel(mappedData.ToList(), paginatedData.TotalCount); 45 | 46 | return paginatedDataViewModel; 47 | } 48 | 49 | public async Task GetCustomer(int id) 50 | { 51 | return _customerViewModelMapper.MapModel(await _customerRepository.GetById(id)); 52 | } 53 | 54 | public async Task IsExists(string key, string value) 55 | { 56 | return await _customerRepository.IsExists(key, value); 57 | } 58 | 59 | public async Task IsExistsForUpdate(int id, string key, string value) 60 | { 61 | return await _customerRepository.IsExistsForUpdate(id, key, value); 62 | } 63 | 64 | public async Task Create(CustomerViewModel model) 65 | { 66 | //Mapping through AutoMapper 67 | var entity = _customerMapper.MapModel(model); 68 | entity.EntryDate = DateTime.Now; 69 | 70 | return _customerViewModelMapper.MapModel(await _customerRepository.Create(entity)); 71 | } 72 | 73 | public async Task Update(CustomerViewModel model) 74 | { 75 | var existingData = await _customerRepository.GetById(model.Id); 76 | 77 | //Manual mapping 78 | existingData.FullName = model.FullName; 79 | existingData.Email = model.Email; 80 | existingData.UpdateDate = DateTime.Now; 81 | 82 | await _customerRepository.Update(existingData); 83 | } 84 | 85 | public async Task Delete(int id) 86 | { 87 | var entity = await _customerRepository.GetById(id); 88 | await _customerRepository.Delete(entity); 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Project.Core/Services/OrderService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IMapper; 4 | using Project.Core.Interfaces.IRepositories; 5 | using Project.Core.Interfaces.IServices; 6 | 7 | namespace Project.Core.Services 8 | { 9 | public class OrderService : IOrderService 10 | { 11 | private readonly IBaseMapper _orderViewModelMapper; 12 | private readonly IBaseMapper _orderMapper; 13 | private readonly IOrderRepository _orderRepository; 14 | private readonly IOrderDetailsRepository _orderDetailsRepository; 15 | 16 | public OrderService( 17 | IBaseMapper orderViewModelMapper, 18 | IBaseMapper orderMapper, 19 | IOrderRepository orderRepository, 20 | IOrderDetailsRepository orderDetailsRepository) 21 | { 22 | _orderMapper = orderMapper; 23 | _orderViewModelMapper = orderViewModelMapper; 24 | _orderRepository = orderRepository; 25 | _orderDetailsRepository = orderDetailsRepository; 26 | } 27 | 28 | public async Task> GetOrders() 29 | { 30 | return _orderViewModelMapper.MapList(await _orderRepository.GetAll()); 31 | } 32 | 33 | public async Task> GetPaginatedOrders(int pageNumber, int pageSize) 34 | { 35 | //Get peginated data 36 | var paginatedData = await _orderRepository.GetPaginatedData(pageNumber, pageSize); 37 | 38 | var mappedData = new List(); 39 | 40 | //Mapping Process 1 41 | mappedData.AddRange( 42 | paginatedData.Data.Select(item => new OrderViewModel 43 | { 44 | Id = item.Id, 45 | CustomerId = item.CustomerId, 46 | CustomerName = item?.Customer?.FullName, 47 | TotalBill = item.TotalBill, 48 | TotalQuantity = item.TotalQuantity, 49 | Description = item.Description, 50 | ProcessingData = item.ProcessingData, 51 | OrderDetails = item.OrderDetails.Select(orderDetail => new OrderDetailsViewModel 52 | { 53 | OrderId = orderDetail.Id, 54 | ProductId = orderDetail.ProductId, 55 | ProductName = orderDetail?.Product?.Name, 56 | SellingPrice = orderDetail.SellingPrice, 57 | Quantity = orderDetail.Quantity, 58 | Description = orderDetail.Description 59 | }).ToList() 60 | }) 61 | ); 62 | 63 | 64 | //Mapping Process 2 65 | //foreach (var item in paginatedData.Data) 66 | //{ 67 | // var orderData = new OrderViewModel 68 | // { 69 | // Id = item.Id, 70 | // CustomerId = item.CustomerId, 71 | // CustomerName = item?.Customer?.FullName, 72 | // TotalBill = item.TotalBill, 73 | // TotalQuantity = item.TotalQuantity, 74 | // Description = item.Description, 75 | // ProcessingData = item.ProcessingData, 76 | // OrderDetails = item.OrderDetails.Select(orderDetail => new OrderDetailsViewModel 77 | // { 78 | // OrderId = orderDetail.Id, 79 | // ProductId = orderDetail.ProductId, 80 | // ProductName = orderDetail?.Product?.Name, 81 | // SellingPrice = orderDetail.SellingPrice, 82 | // Quantity = orderDetail.Quantity, 83 | // Description = orderDetail.Description 84 | // }).ToList() 85 | // }; 86 | // mappedData.Add(orderData); 87 | //} 88 | 89 | var paginatedDataViewModel = new PaginatedDataViewModel(mappedData, paginatedData.TotalCount); 90 | 91 | return paginatedDataViewModel; 92 | } 93 | 94 | public async Task GetOrder(int id) 95 | { 96 | //var orderData = await _orderRepository.GetById(id); 97 | //return _orderViewModelMapper.MapModel(orderData); 98 | 99 | return await _orderRepository.GetOrderById(id); 100 | } 101 | 102 | public async Task IsExists(string key, string value) 103 | { 104 | return await _orderRepository.IsExists(key, value); 105 | } 106 | 107 | public async Task IsExistsForUpdate(int id, string key, string value) 108 | { 109 | return await _orderRepository.IsExistsForUpdate(id, key, value); 110 | } 111 | 112 | public async Task Create(OrderViewModel model) 113 | { 114 | //Manual mapping 115 | var order = new Order 116 | { 117 | CustomerId = model.CustomerId, 118 | TotalBill = model.TotalBill, 119 | TotalQuantity = model.TotalQuantity, 120 | ProcessingData = model.ProcessingData, 121 | Description = model.Description, 122 | EntryDate = DateTime.Now 123 | }; 124 | var orderData = await _orderRepository.Create(order); 125 | var orderDetails = new List(); 126 | 127 | foreach (var item in model.OrderDetails) 128 | { 129 | orderDetails.Add(new OrderDetails 130 | { 131 | OrderId = orderData.Id, 132 | ProductId = item.ProductId, 133 | SellingPrice = item.SellingPrice, 134 | Quantity = item.Quantity, 135 | Description = item.Description, 136 | EntryDate = DateTime.Now 137 | }); 138 | } 139 | await _orderDetailsRepository.CreateRange(orderDetails); 140 | 141 | return _orderViewModelMapper.MapModel(orderData); 142 | } 143 | 144 | public async Task Update(OrderViewModel model) 145 | { 146 | var existingData = await _orderRepository.GetById(model.Id); 147 | 148 | //Manual mapping 149 | //existingData.FullName = model.FullName; 150 | //existingData.Email = model.Email; 151 | existingData.UpdateDate = DateTime.Now; 152 | 153 | await _orderRepository.Update(existingData); 154 | } 155 | 156 | public async Task Delete(int id) 157 | { 158 | var entity = await _orderRepository.GetById(id); 159 | await _orderRepository.Delete(entity); 160 | } 161 | 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Project.Core/Services/ProductService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Exceptions; 4 | using Project.Core.Interfaces.IMapper; 5 | using Project.Core.Interfaces.IRepositories; 6 | using Project.Core.Interfaces.IServices; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace Project.Core.Services 14 | { 15 | public class ProductService : IProductService 16 | { 17 | private readonly IBaseMapper _productViewModelMapper; 18 | private readonly IBaseMapper _productMapper; 19 | private readonly IProductRepository _productRepository; 20 | 21 | public ProductService( 22 | IBaseMapper productViewModelMapper, 23 | IBaseMapper productMapper, 24 | IProductRepository productRepository) 25 | { 26 | _productMapper = productMapper; 27 | _productViewModelMapper = productViewModelMapper; 28 | _productRepository = productRepository; 29 | } 30 | 31 | public async Task> GetProducts() 32 | { 33 | return _productViewModelMapper.MapList(await _productRepository.GetAll()); 34 | } 35 | 36 | public async Task> GetPaginatedProducts(int pageNumber, int pageSize) 37 | { 38 | //Get peginated data 39 | var paginatedData = await _productRepository.GetPaginatedData(pageNumber, pageSize); 40 | 41 | //Map data with ViewModel 42 | var mappedData = _productViewModelMapper.MapList(paginatedData.Data); 43 | 44 | var paginatedDataViewModel = new PaginatedDataViewModel(mappedData.ToList(), paginatedData.TotalCount); 45 | 46 | return paginatedDataViewModel; 47 | } 48 | 49 | public async Task GetProduct(int id) 50 | { 51 | return _productViewModelMapper.MapModel(await _productRepository.GetById(id)); 52 | } 53 | 54 | public async Task IsExists(string key, string value) 55 | { 56 | return await _productRepository.IsExists(key, value); 57 | } 58 | 59 | public async Task IsExistsForUpdate(int id, string key, string value) 60 | { 61 | return await _productRepository.IsExistsForUpdate(id, key, value); 62 | } 63 | 64 | public async Task Create(ProductViewModel model) 65 | { 66 | //Mapping through AutoMapper 67 | var entity = _productMapper.MapModel(model); 68 | entity.EntryDate = DateTime.Now; 69 | return _productViewModelMapper.MapModel(await _productRepository.Create(entity)); 70 | } 71 | 72 | public async Task Update(ProductViewModel model) 73 | { 74 | var existingData = await _productRepository.GetById(model.Id); 75 | 76 | //Manual mapping 77 | existingData.Code = model.Code; 78 | existingData.Name = model.Name; 79 | existingData.Price = model.Price; 80 | existingData.Description = model.Description; 81 | existingData.IsActive = model.IsActive; 82 | existingData.UpdateDate = DateTime.Now; 83 | 84 | await _productRepository.Update(existingData); 85 | } 86 | 87 | public async Task Delete(int id) 88 | { 89 | var entity = await _productRepository.GetById(id); 90 | await _productRepository.Delete(entity); 91 | } 92 | 93 | public async Task PriceCheck(int productId) 94 | { 95 | return await _productRepository.PriceCheck(productId); 96 | } 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Project.Infrastructure/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore; 4 | using Project.Core.Entities.General; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Project.Infrastructure.Data 12 | { 13 | public class ApplicationDbContext : IdentityDbContext 14 | { 15 | public ApplicationDbContext(DbContextOptions options) : base(options) 16 | { 17 | } 18 | 19 | #region DbSet Section 20 | public DbSet Products { get; set; } 21 | public DbSet Customers { get; set; } 22 | public DbSet Orders { get; set; } 23 | public DbSet OrderDetails { get; set; } 24 | 25 | #endregion 26 | 27 | protected override void OnModelCreating(ModelBuilder builder) 28 | { 29 | base.OnModelCreating(builder); 30 | 31 | ApplicationDbContextConfigurations.Configure(builder); 32 | ApplicationDbContextConfigurations.SeedData(builder); 33 | 34 | } 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Project.Infrastructure/Data/ApplicationDbContextConfigurations.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.EntityFrameworkCore; 4 | using Project.Core.Entities.General; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Project.Infrastructure.Data 12 | { 13 | public class ApplicationDbContextConfigurations 14 | { 15 | public static void Configure(ModelBuilder modelBuilder) 16 | { 17 | modelBuilder.Entity().ToTable("Users"); 18 | modelBuilder.Entity().ToTable("Roles"); 19 | 20 | // Add any additional entity configurations here 21 | } 22 | 23 | public static void SeedData(ModelBuilder modelBuilder) 24 | { 25 | // Add any seed data here 26 | 27 | // Generate fake customer data. 28 | var customerIds = Enumerable.Range(1, 100).ToList(); 29 | var fakerSeedCustomers = new Faker() 30 | .RuleFor(c => c.Id, f => 31 | { 32 | // Pop the next unique Id from the list 33 | var index = f.Random.Int(0, customerIds.Count() - 1); 34 | var id = customerIds[index]; 35 | customerIds.RemoveAt(index); 36 | return id; 37 | }) 38 | .RuleFor(c => c.FullName, f => f.Name.FullName()) 39 | .RuleFor(c => c.Email, f => f.Internet.Email()) 40 | .RuleFor(c => c.Balance, f => f.Random.Decimal(1, 10000).OrNull(f)); 41 | 42 | var fakeCustomers = fakerSeedCustomers.Generate(100); 43 | modelBuilder.Entity().HasData(fakeCustomers); 44 | 45 | // Generate fake product data. 46 | var productIds = Enumerable.Range(1, 100).ToList(); 47 | var fakerSeedProducts = new Faker() 48 | .RuleFor(c => c.Id, f => 49 | { 50 | var index = f.Random.Int(0, productIds.Count() - 1); 51 | var id = productIds[index]; 52 | productIds.RemoveAt(index); 53 | return id; 54 | }) 55 | .RuleFor(p => p.Code, f => f.Random.AlphaNumeric(6)) 56 | .RuleFor(p => p.Name, f => f.Commerce.ProductName()) 57 | .RuleFor(p => p.Price, f => f.Random.Double(1, 1000)) 58 | .RuleFor(p => p.Quantity, f => f.Random.Int(1, 100)) 59 | .RuleFor(p => p.IsActive, f => f.Random.Bool()); 60 | 61 | var fakeProducts = fakerSeedProducts.Generate(100); 62 | modelBuilder.Entity().HasData(fakeProducts); 63 | 64 | 65 | // Generate fake order data. 66 | var orderIds = Enumerable.Range(1, 1000).ToList(); 67 | 68 | var fakerSeedOrders = new Faker() 69 | .RuleFor(c => c.Id, f => 70 | { 71 | var index = f.Random.Int(0, orderIds.Count() - 1); 72 | var id = orderIds[index]; 73 | orderIds.RemoveAt(index); 74 | return id; 75 | }) 76 | .RuleFor(o => o.CustomerId, f => f.Random.Int(1, 100)) 77 | .RuleFor(o => o.TotalBill, f => f.Random.Decimal(10, 10000)) 78 | .RuleFor(o => o.TotalQuantity, f => f.Random.Int(1, 1000)) 79 | .RuleFor(o => o.ProcessingData, f => f.Date.Past()); 80 | 81 | var fakeOrders = fakerSeedOrders.Generate(1000); 82 | modelBuilder.Entity().HasData(fakeOrders); 83 | 84 | // Generate fake order details data. 85 | var orderDetailsIds = Enumerable.Range(1, 3000).ToList(); 86 | 87 | var fakerSeedOrderDetails = new Faker() 88 | .RuleFor(c => c.Id, f => 89 | { 90 | var index = f.Random.Int(0, orderDetailsIds.Count() - 1); 91 | var id = orderDetailsIds[index]; 92 | orderDetailsIds.RemoveAt(index); 93 | return id; 94 | }) 95 | .RuleFor(od => od.OrderId, f => f.Random.Int(1, 1000)) 96 | .RuleFor(od => od.ProductId, f => f.Random.Int(1, 100)) 97 | .RuleFor(od => od.Quantity, f => f.Random.Int(1, 1000)) 98 | .RuleFor(od => od.SellingPrice, f => f.Random.Decimal(1, 1000)); 99 | 100 | var fakeOrderDetails = fakerSeedOrderDetails.Generate(3000); 101 | modelBuilder.Entity().HasData(fakeOrderDetails); 102 | 103 | 104 | } 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Project.Infrastructure/Project.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/BaseRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Project.Core.Entities.Business; 3 | using Project.Core.Exceptions; 4 | using Project.Core.Interfaces.IRepositories; 5 | using Project.Infrastructure.Data; 6 | using System.Linq.Expressions; 7 | 8 | namespace Project.Infrastructure.Repositories 9 | { 10 | //Unit of Work Pattern 11 | public class BaseRepository : IBaseRepository where T : class 12 | { 13 | protected readonly ApplicationDbContext _dbContext; 14 | protected DbSet DbSet => _dbContext.Set(); 15 | 16 | public BaseRepository(ApplicationDbContext dbContext) 17 | { 18 | _dbContext = dbContext; 19 | } 20 | 21 | public async Task> GetAll() 22 | { 23 | var data = await _dbContext.Set() 24 | .AsNoTracking() 25 | .ToListAsync(); 26 | 27 | return data; 28 | } 29 | 30 | public virtual async Task> GetPaginatedData(int pageNumber, int pageSize) 31 | { 32 | var query = _dbContext.Set() 33 | .Skip((pageNumber - 1) * pageSize) 34 | .Take(pageSize) 35 | .AsNoTracking(); 36 | 37 | var data = await query.ToListAsync(); 38 | var totalCount = await _dbContext.Set().CountAsync(); 39 | 40 | return new PaginatedDataViewModel(data, totalCount); 41 | } 42 | 43 | public async Task GetById(Tid id) 44 | { 45 | var data = await _dbContext.Set().FindAsync(id); 46 | if (data == null) 47 | throw new NotFoundException("No data found"); 48 | return data; 49 | } 50 | 51 | public async Task IsExists(string key, Tvalue value) 52 | { 53 | var parameter = Expression.Parameter(typeof(T), "x"); 54 | var property = Expression.Property(parameter, key); 55 | var constant = Expression.Constant(value); 56 | var equality = Expression.Equal(property, constant); 57 | var lambda = Expression.Lambda>(equality, parameter); 58 | 59 | return await _dbContext.Set().AnyAsync(lambda); 60 | } 61 | 62 | //Before update existence check 63 | public async Task IsExistsForUpdate(Tid id, string key, string value) 64 | { 65 | var parameter = Expression.Parameter(typeof(T), "x"); 66 | var property = Expression.Property(parameter, key); 67 | var constant = Expression.Constant(value); 68 | var equality = Expression.Equal(property, constant); 69 | 70 | var idProperty = Expression.Property(parameter, "Id"); 71 | var idEquality = Expression.NotEqual(idProperty, Expression.Constant(id)); 72 | 73 | var combinedExpression = Expression.AndAlso(equality, idEquality); 74 | var lambda = Expression.Lambda>(combinedExpression, parameter); 75 | 76 | return await _dbContext.Set().AnyAsync(lambda); 77 | } 78 | 79 | 80 | public async Task Create(T model) 81 | { 82 | await _dbContext.Set().AddAsync(model); 83 | await _dbContext.SaveChangesAsync(); 84 | return model; 85 | } 86 | 87 | public async Task CreateRange(List model) 88 | { 89 | await _dbContext.Set().AddRangeAsync(model); 90 | await _dbContext.SaveChangesAsync(); 91 | } 92 | 93 | public async Task Update(T model) 94 | { 95 | _dbContext.Set().Update(model); 96 | await _dbContext.SaveChangesAsync(); 97 | } 98 | 99 | public async Task Delete(T model) 100 | { 101 | _dbContext.Set().Remove(model); 102 | await _dbContext.SaveChangesAsync(); 103 | } 104 | 105 | public async Task SaveChangeAsync() 106 | { 107 | await _dbContext.SaveChangesAsync(); 108 | } 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/CustomerRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IRepositories; 4 | using Project.Infrastructure.Data; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Project.Infrastructure.Repositories 12 | { 13 | public class CustomerRepository : BaseRepository, ICustomerRepository 14 | { 15 | public CustomerRepository(ApplicationDbContext dbContext) : base(dbContext) 16 | { 17 | } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/OrderDetailsRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IRepositories; 4 | using Project.Infrastructure.Data; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Project.Infrastructure.Repositories 12 | { 13 | public class OrderDetailsRepository : BaseRepository, IOrderDetailsRepository 14 | { 15 | public OrderDetailsRepository(ApplicationDbContext dbContext) : base(dbContext) 16 | { 17 | } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/OrderRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Project.Core.Entities.Business; 3 | using Project.Core.Entities.General; 4 | using Project.Core.Exceptions; 5 | using Project.Core.Interfaces.IRepositories; 6 | using Project.Infrastructure.Data; 7 | 8 | namespace Project.Infrastructure.Repositories 9 | { 10 | public class OrderRepository : BaseRepository, IOrderRepository 11 | { 12 | public OrderRepository(ApplicationDbContext dbContext) : base(dbContext) 13 | { 14 | } 15 | 16 | public override async Task> GetPaginatedData(int pageNumber, int pageSize) 17 | { 18 | var query = _dbContext.Orders 19 | .Include(c => c.Customer) 20 | .Include(o => o.OrderDetails) 21 | .ThenInclude(od => od.Product) 22 | .Skip((pageNumber - 1) * pageSize) 23 | .Take(pageSize) 24 | .AsNoTracking(); 25 | 26 | var data = await query.ToListAsync(); 27 | var totalCount = await _dbContext.Orders.CountAsync(); 28 | 29 | return new PaginatedDataViewModel(data, totalCount); 30 | } 31 | 32 | public async Task GetOrderById(int id) 33 | { 34 | // If want to use LINQ Query Syntax 35 | //var query = from order in _dbContext.Orders 36 | // where order.Id == id 37 | // join customer in _dbContext.Customers on order.CustomerId equals customer.Id 38 | // join orderDetails in _dbContext.OrderDetails on order.Id equals orderDetails.OrderId 39 | // join product in _dbContext.Products on orderDetails.ProductId equals product.Id 40 | // select new { Order = order, OrderDetails = orderDetails, Product = product, Customer = customer }; 41 | 42 | //var result = await query.AsNoTracking().ToListAsync(); 43 | //var data = result.First().Order; 44 | //var orderDetailsData = result; 45 | 46 | //If want to use LINQ Method Syntax 47 | var data = await _dbContext.Orders.Where(x => x.Id == id) 48 | .Include(c => c.Customer) 49 | .Include(x => x.OrderDetails) 50 | .ThenInclude(od => od.Product) 51 | .FirstOrDefaultAsync(); 52 | 53 | if (data == null) 54 | { 55 | throw new NotFoundException("No data found"); 56 | } 57 | 58 | var orderData = new OrderViewModel 59 | { 60 | Id = data.Id, 61 | CustomerId = data.CustomerId, 62 | CustomerName = data?.Customer?.FullName, 63 | TotalBill = data.TotalBill, 64 | TotalQuantity = data.TotalQuantity, 65 | Description = data.Description, 66 | ProcessingData = data.ProcessingData, 67 | OrderDetails = data.OrderDetails.Select(orderDetail => new OrderDetailsViewModel 68 | { 69 | OrderId = orderDetail.Id, 70 | ProductId = orderDetail.ProductId, 71 | ProductName = orderDetail?.Product?.Name, 72 | SellingPrice = orderDetail.SellingPrice, 73 | Quantity = orderDetail.Quantity, 74 | Description = orderDetail.Description 75 | }).ToList() 76 | }; 77 | 78 | return orderData; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/ProductRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IRepositories; 4 | using Project.Infrastructure.Data; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Project.Infrastructure.Repositories 12 | { 13 | public class ProductRepository : BaseRepository, IProductRepository 14 | { 15 | public ProductRepository(ApplicationDbContext dbContext) : base(dbContext) 16 | { 17 | } 18 | 19 | public async Task PriceCheck(int productId) 20 | { 21 | var price = await _dbContext.Products 22 | .Where(x => x.Id == productId) 23 | .Select(x => x.Price) 24 | .FirstOrDefaultAsync(); 25 | return price; 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Project.UnitTest/API/ProductControllerTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using Moq; 4 | using Project.API.Controllers; 5 | using Project.Core.Entities.Business; 6 | using Project.Core.Entities.General; 7 | using Project.Core.Interfaces.IServices; 8 | 9 | namespace Project.UnitTest.API 10 | { 11 | public class ProductControllerTests 12 | { 13 | private Mock _productServiceMock; 14 | private Mock> _loggerMock; 15 | private ProductController _productController; 16 | 17 | [SetUp] 18 | public void Setup() 19 | { 20 | _productServiceMock = new Mock(); 21 | _loggerMock = new Mock>(); 22 | _productController = new ProductController(_loggerMock.Object, _productServiceMock.Object); 23 | } 24 | 25 | [Test] 26 | public async Task Get_ReturnsViewWithListOfProducts() 27 | { 28 | // Arrange 29 | var products = new List 30 | { 31 | new ProductViewModel { Id = 1, Code = "P001", Name = "Product A", Price = 9.99f, IsActive = true }, 32 | new ProductViewModel { Id = 2, Code = "P002", Name = "Product B", Price = 19.99f, IsActive = true } 33 | }; 34 | 35 | _productServiceMock.Setup(service => service.GetProducts()) 36 | .ReturnsAsync(products); 37 | 38 | // Act 39 | var result = await _productController.Get(); 40 | 41 | // Assert 42 | Assert.IsInstanceOf(result); 43 | var okObjectResult = (OkObjectResult)result; 44 | Assert.NotNull(okObjectResult); 45 | 46 | var model = (IEnumerable)okObjectResult.Value; 47 | Assert.NotNull(model); 48 | Assert.That(model.Count(), Is.EqualTo(products.Count)); 49 | 50 | } 51 | 52 | // Add more test methods for other controller actions, such as Create, Update, Delete, etc. 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Project.UnitTest/Core/ProductServiceTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using Project.Core.Entities.Business; 3 | using Project.Core.Entities.General; 4 | using Project.Core.Interfaces.IMapper; 5 | using Project.Core.Interfaces.IRepositories; 6 | using Project.Core.Services; 7 | 8 | namespace Project.UnitTest 9 | { 10 | public class ProductServiceTests 11 | { 12 | private Mock> _productViewModelMapperMock; 13 | private Mock> _productMapperMock; 14 | private Mock _productRepositoryMock; 15 | 16 | [SetUp] 17 | public void Setup() 18 | { 19 | _productViewModelMapperMock = new Mock>(); 20 | _productMapperMock = new Mock>(); 21 | _productRepositoryMock = new Mock(); 22 | } 23 | 24 | [Test] 25 | public async Task CreateProductAsync_ValidProduct_ReturnsCreatedProductViewModel() 26 | { 27 | // Arrange 28 | var productService = new ProductService( 29 | _productViewModelMapperMock.Object, 30 | _productMapperMock.Object, 31 | _productRepositoryMock.Object); 32 | 33 | var newProductViewModel = new ProductViewModel 34 | { 35 | Code = "P001", 36 | Name = "Sample Product", 37 | Price = 9.99f, 38 | Description = "Sample description", 39 | IsActive = true 40 | }; 41 | 42 | var createdProduct = new Product 43 | { 44 | Code = "P001", 45 | Name = "Sample Product", 46 | Price = 9.99f, 47 | Description = "Sample description", 48 | IsActive = true 49 | }; 50 | 51 | _productMapperMock.Setup(mapper => mapper.MapModel(newProductViewModel)) 52 | .Returns(createdProduct); 53 | 54 | _productRepositoryMock.Setup(repo => repo.Create(createdProduct)) 55 | .ReturnsAsync(createdProduct); 56 | 57 | _productViewModelMapperMock.Setup(mapper => mapper.MapModel(createdProduct)) 58 | .Returns(newProductViewModel); 59 | 60 | // Act 61 | var result = await productService.Create(newProductViewModel); 62 | 63 | // Assert 64 | Assert.NotNull(result); 65 | Assert.That(result.Code, Is.EqualTo(newProductViewModel.Code)); 66 | // Additional assertions for other properties 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /Project.UnitTest/Infrastructure/ProductRepositoryTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.ChangeTracking; 3 | using Moq; 4 | using Project.Core.Entities.General; 5 | using Project.Infrastructure.Data; 6 | using Project.Infrastructure.Repositories; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace Project.UnitTest.Infrastructure 14 | { 15 | public class ProductRepositoryTests 16 | { 17 | private Mock _dbContextMock; 18 | private ProductRepository _productRepository; 19 | 20 | [SetUp] 21 | public void Setup() 22 | { 23 | _dbContextMock = new Mock(new DbContextOptions()); 24 | _productRepository = new ProductRepository(_dbContextMock.Object); 25 | } 26 | 27 | [Test] 28 | public async Task AddAsync_ValidProduct_ReturnsAddedProduct() 29 | { 30 | 31 | // Arrange 32 | var newProduct = new Product 33 | { 34 | Code = "P001", 35 | Name = "Sample Product", 36 | Price = 9.99f, 37 | IsActive = true 38 | }; 39 | 40 | var productDbSetMock = new Mock>(); 41 | 42 | _dbContextMock.Setup(db => db.Set()) 43 | .Returns(productDbSetMock.Object); 44 | 45 | productDbSetMock.Setup(dbSet => dbSet.AddAsync(newProduct, default)) 46 | .ReturnsAsync((EntityEntry)null); 47 | 48 | // Act 49 | var result = await _productRepository.Create(newProduct); 50 | 51 | 52 | // Assert 53 | Assert.NotNull(result); 54 | Assert.That(result, Is.EqualTo(newProduct)); 55 | } 56 | 57 | // Add more test methods for other repository operations, such as GetByIdAsync, UpdateAsync, DeleteAsync, etc. 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Project.UnitTest/Project.UnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Project.UnitTest/Usings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Structured API Project - ASP.NET Core 2 | 3 | This template is for a clean structured ASP.NET Core API project, following the RESTful principles, Clean Architecture principles, SOLID design principles, implementing the Dependency Injection, Repository, and Unit of Work design pattern, and utilizing Entity Framework Core for data access. It provides a standardized structure and organization for building robust and maintainable ASP.NET Core API applications with complete CRUD (Create, Read, Update, Delete) operations. 4 | 5 | #### Reference 6 | You can visit my blog post- [Clean Structured API Project – ASP.NET Core](https://binarybytez.com/clean-structured-api-project/) 7 | 8 | ## Project Structure 9 | 10 | The project structure is designed to promote separation of concerns and modularity, making it easier to understand, test, and maintain the application. 11 | 12 | ``` 13 | ├── src 14 | │ ├── Core # Contains the core business logic and domain models, view models, etc. 15 | │ ├── Infrastructure # Contains infrastructure concerns such as data access, external services, etc. 16 | │ └── API # Contains the API layer, including controllers, extensions, etc. 17 | ├── tests 18 | │ ├── Core.Tests # Contains unit tests for the core layer 19 | │ ├── Infrastructure.Tests # Contains unit tests for the infrastructure layer 20 | │ └── API.Tests # Contains unit tests for the API layer 21 | └── README.md # Project documentation (you are here!) 22 | ``` 23 | 24 | ## REST API 25 | 26 | The API project contains the controllers responsible for handling HTTP requests and responses, adhering to RESTful principles. Here's an overview of the key components involved in building RESTful APIs using ASP.NET Core: 27 | 28 | 1. **Controllers**: The `API` project contains controllers that handle HTTP requests and responses. Each controller is responsible for a specific resource or entity. Controllers define HTTP methods (GET, POST, PUT, DELETE) that map to specific actions for CRUD operations on entities. 29 | 30 | 2. **Models/DTOs**: The `Core` project may contain Data Transfer Objects (DTOs) that represent the data to be sent over the API. DTOs help in decoupling the client's data format from the server's data format. 31 | 32 | 3. **Routing**: The routing mechanism in ASP.NET Core maps incoming HTTP requests to the appropriate controller and action method based on the URL. RESTful APIs typically use a resource-based URL pattern. 33 | 34 | 4. **HTTP Methods**: RESTful APIs use standard HTTP methods (GET, POST, PUT, DELETE) to perform CRUD operations on resources. Each HTTP method corresponds to a specific action on the API. 35 | 36 | 5. **Status Codes**: RESTful APIs use standard HTTP status codes to indicate the success or failure of an API request. For example, 200 (OK) for successful GET requests, 201 (Created) for successful POST requests, 204 (No Content) for successful DELETE requests, etc. 37 | 38 | 6. **Validation**: RESTful APIs should include proper validation logic to ensure that incoming data is valid and adheres to the expected format. 39 | 40 | ## Getting Started 41 | 42 | To use this project template, follow the steps below: 43 | 44 | 1. Ensure that you have the .NET 7 SDK installed on your machine. 45 | 2. Clone or download this repository to your local machine. 46 | 3. Open the solution in your preferred IDE (e.g., Visual Studio, Visual Studio Code). 47 | 4. Build the solution to restore NuGet packages and compile the code. 48 | 5. Configure the necessary database connection settings in the `appsettings.json` file of the Infrastructure project. 49 | 6. Open the Package Manager Console, select `Project.Infrastructure` project, and run the `Update-Database` command to create the database. 50 | 7. Run the application by starting the `Project.API` project. 51 | 52 | ## Project Features 53 | 54 | This project template includes the following features: 55 | 56 | - **Clean Architecture**: The project is structured according to the principles of [Clean Architecture](https://binarybytez.com/understanding-clean-architecture/), which promotes separation of concerns and a clear division of responsibilities. 57 | - **SOLID Design Principles**: The code adheres to [SOLID principles](https://binarybytez.com/mastering-solid-design-principles/) (Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion), making it easier to maintain and extend. 58 | - **Repository Pattern**: The repository pattern abstracts the data access layer and provides a consistent interface for working with data. 59 | - **Unit of Work Pattern**: The unit of work pattern helps manage transactions and ensures consistency when working with multiple repositories. 60 | - **Entity Framework Core**: The project utilizes Entity Framework Core as the ORM (Object-Relational Mapping) tool for data access. 61 | - **ASP.NET Core API**: The project includes an ASP.NET Core API project that serves as the API layer, handling HTTP requests and responses. 62 | - **CRUD Operations**: The project template provides a foundation for implementing complete CRUD (Create, Read, Update, Delete) operations on entities using Entity Framework Core. 63 | - **Dependency Injection**: The project utilizes the built-in [dependency injection](https://binarybytez.com/understanding-dependency-injection/) container in ASP.NET Core, making it easy to manage and inject dependencies throughout the application. 64 | - **Unit Testing**: The solution includes separate test projects for unit testing the core, infrastructure, and API layers. 65 | 66 | ## Usage 67 | 68 | The [project template](https://binarybytez.com/clean-structured-api-project/) provides a starting point for building RESTful APIs using ASP.NET Core. You can modify and extend the existing code to suit your specific application requirements. Here's an overview of the key components involved in building RESTful APIs: 69 | 70 | 1. **Models**: The `Core` project contains the domain models representing the entities you want to perform CRUD operations on. Update the models or add new ones according to your domain. 71 | 2. **Repositories**: The `Infrastructure` project contains repository implementations that handle data access operations using Entity Framework Core. Modify the repositories or create new ones to match your entity models and database structure. 72 | 3. **Services**: The `Core` project contains services that encapsulate the business logic and orchestrate the operations on repositories. Update or create new services to handle CRUD operations on your entities. 73 | 4. **Controllers**: The `API` project contains controllers that handle HTTP requests and responses. Update or create new controllers to expose the CRUD endpoints for your entities. Implement the appropriate HTTP methods (GET, POST, PUT, DELETE) and perform actions on the core services accordingly. 74 | 75 | Make sure to update the routes, validation, and error-handling logic to align with your application requirements and [best practices](https://binarybytez.com/performance-optimization-and-monitoring-in-asp-net-core/). 76 | 77 | ## Authors 78 | 79 | If you have any questions or need further assistance, please contact the project author at [@kawser2133](https://www.github.com/kawser2133) || [![linkedin](https://img.shields.io/badge/linkedin-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/kawser2133) 80 | 81 | Buy Me A Coffee
82 | **Thanks for your support!** 83 | 84 | ## Contributing 85 | 86 | I want you to know that contributions to this project are welcome. Please open an issue or submit a pull request if you have any ideas, bug fixes, or improvements. 87 | 88 | ## License 89 | 90 | This project is licensed under the [MIT License](LICENSE). 91 | --------------------------------------------------------------------------------