├── .gitignore ├── Clean.Project.sln ├── LICENSE ├── Project.Core ├── Entities │ ├── Business │ │ ├── PaginatedDataViewModel.cs │ │ └── ProductViewModel.cs │ └── General │ │ ├── Base.cs │ │ └── Product.cs ├── Exceptions │ └── NotFoundException.cs ├── Interfaces │ ├── IMapper │ │ └── IBaseMapper.cs │ ├── IRepositories │ │ ├── IBaseRepository.cs │ │ └── IProductRepository.cs │ └── IServices │ │ └── IProductService.cs ├── Mapper │ └── BaseMapper.cs ├── Project.Core.csproj └── Services │ └── ProductService.cs ├── Project.Infrastructure ├── Data │ ├── ApplicationDbContext.cs │ └── ApplicationDbContextConfigurations.cs ├── Migrations │ ├── 20230703085651_Initial_Migration.Designer.cs │ ├── 20230703085651_Initial_Migration.cs │ ├── 20230712112009_Seed Data.Designer.cs │ ├── 20230712112009_Seed Data.cs │ └── ApplicationDbContextModelSnapshot.cs ├── Project.Infrastructure.csproj └── Repositories │ ├── BaseRepository.cs │ └── ProductRepository.cs ├── Project.UI ├── Controllers │ ├── HomeController.cs │ └── ProductController.cs ├── Extensions │ └── ServiceExtension.cs ├── Middlewares │ └── RequestResponseLoggingMiddleware.cs ├── Program.cs ├── Project.UI.csproj ├── Properties │ └── launchSettings.json ├── Views │ ├── Home │ │ └── Index.cshtml │ ├── Product │ │ ├── Create.cshtml │ │ ├── Details.cshtml │ │ ├── Edit.cshtml │ │ └── Index.cshtml │ ├── Shared │ │ └── _Layout.cshtml │ └── _ViewImports.cshtml ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── css │ └── site.css │ ├── images │ └── nav.png │ └── js │ └── site.js ├── Project.UnitTest ├── Core │ └── ProductServiceTests.cs ├── Infrastructure │ └── ProductRepositoryTests.cs ├── Project.UnitTest.csproj ├── UI │ └── ProductControllerTests.cs └── 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.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.UI", "Project.UI\Project.UI.csproj", "{CAA1C682-F635-4957-B4F2-01680FFD1A14}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Project.UnitTest", "Project.UnitTest\Project.UnitTest.csproj", "{9A138398-C199-404D-9E42-3095A2290A07}" 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 | {CAA1C682-F635-4957-B4F2-01680FFD1A14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {CAA1C682-F635-4957-B4F2-01680FFD1A14}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {CAA1C682-F635-4957-B4F2-01680FFD1A14}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {CAA1C682-F635-4957-B4F2-01680FFD1A14}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {9A138398-C199-404D-9E42-3095A2290A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {9A138398-C199-404D-9E42-3095A2290A07}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {9A138398-C199-404D-9E42-3095A2290A07}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {9A138398-C199-404D-9E42-3095A2290A07}.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.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.General 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 float Price { get; set; } 19 | [StringLength(maximumLength: 350)] 20 | public string? Description { get; set; } 21 | public bool IsActive { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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/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 float Price { get; set; } 20 | [StringLength(maximumLength: 350)] 21 | public string? Description { get; set; } 22 | public bool IsActive { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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 Update(T model); 22 | Task Delete(T model); 23 | Task SaveChangeAsync(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | } 13 | } 14 | -------------------------------------------------------------------------------- /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 | } 22 | } 23 | -------------------------------------------------------------------------------- /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/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 | 70 | return _productViewModelMapper.MapModel(await _productRepository.Create(entity)); 71 | } 72 | 73 | public async Task Update(ProductViewModel model) 74 | { 75 | var existingData = await _productRepository.GetById(model.Id); 76 | 77 | //Manual mapping 78 | existingData.Code = model.Code; 79 | existingData.Name = model.Name; 80 | existingData.Price = model.Price; 81 | existingData.Description = model.Description; 82 | existingData.IsActive = model.IsActive; 83 | existingData.UpdateDate = DateTime.Now; 84 | 85 | await _productRepository.Update(existingData); 86 | } 87 | 88 | public async Task Delete(int id) 89 | { 90 | var entity = await _productRepository.GetById(id); 91 | await _productRepository.Delete(entity); 92 | } 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /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 | 22 | #endregion 23 | 24 | protected override void OnModelCreating(ModelBuilder builder) 25 | { 26 | base.OnModelCreating(builder); 27 | 28 | ApplicationDbContextConfigurations.Configure(builder); 29 | ApplicationDbContextConfigurations.SeedData(builder); 30 | 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Project.Infrastructure/Data/ApplicationDbContextConfigurations.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.EntityFrameworkCore; 3 | using Project.Core.Entities.General; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Project.Infrastructure.Data 11 | { 12 | public class ApplicationDbContextConfigurations 13 | { 14 | public static void Configure(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder.Entity().ToTable("Users"); 17 | modelBuilder.Entity().ToTable("Roles"); 18 | 19 | // Add any additional entity configurations here 20 | } 21 | 22 | public static void SeedData(ModelBuilder modelBuilder) 23 | { 24 | // Add any seed data here 25 | modelBuilder.Entity().HasData( 26 | new Product { Id = 1, Code = "P001", Name = "Product 1", Price = 9.99f, IsActive = true }, 27 | new Product { Id = 2, Code = "P002", Name = "Product 2", Price = 12.00f, IsActive = true }, 28 | new Product { Id = 3, Code = "P003", Name = "Product 3", Price = 13.00f, IsActive = true }, 29 | new Product { Id = 4, Code = "P004", Name = "Product 4", Price = 14.00f, IsActive = true }, 30 | new Product { Id = 5, Code = "P005", Name = "Product 5", Price = 15.00f, IsActive = true }, 31 | new Product { Id = 6, Code = "P006", Name = "Product 6", Price = 16.00f, IsActive = true }, 32 | new Product { Id = 7, Code = "P007", Name = "Product 7", Price = 17.00f, IsActive = true }, 33 | new Product { Id = 8, Code = "P008", Name = "Product 8", Price = 18.00f, IsActive = true }, 34 | new Product { Id = 9, Code = "P009", Name = "Product 9", Price = 19.00f, IsActive = true }, 35 | new Product { Id = 10, Code = "P010", Name = "Product 10", Price = 19.99f, IsActive = true } 36 | ); 37 | 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Project.Infrastructure/Migrations/20230703085651_Initial_Migration.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | using Project.Infrastructure.Data; 9 | 10 | #nullable disable 11 | 12 | namespace Project.Infrastructure.Migrations 13 | { 14 | [DbContext(typeof(ApplicationDbContext))] 15 | [Migration("20230703085651_Initial_Migration")] 16 | partial class Initial_Migration 17 | { 18 | /// 19 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 | { 21 | #pragma warning disable 612, 618 22 | modelBuilder 23 | .HasAnnotation("ProductVersion", "7.0.8") 24 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 25 | 26 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 27 | 28 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 29 | { 30 | b.Property("Id") 31 | .HasColumnType("nvarchar(450)"); 32 | 33 | b.Property("ConcurrencyStamp") 34 | .IsConcurrencyToken() 35 | .HasColumnType("nvarchar(max)"); 36 | 37 | b.Property("Name") 38 | .HasMaxLength(256) 39 | .HasColumnType("nvarchar(256)"); 40 | 41 | b.Property("NormalizedName") 42 | .HasMaxLength(256) 43 | .HasColumnType("nvarchar(256)"); 44 | 45 | b.HasKey("Id"); 46 | 47 | b.HasIndex("NormalizedName") 48 | .IsUnique() 49 | .HasDatabaseName("RoleNameIndex") 50 | .HasFilter("[NormalizedName] IS NOT NULL"); 51 | 52 | b.ToTable("Roles", (string)null); 53 | }); 54 | 55 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 56 | { 57 | b.Property("Id") 58 | .ValueGeneratedOnAdd() 59 | .HasColumnType("int"); 60 | 61 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 62 | 63 | b.Property("ClaimType") 64 | .HasColumnType("nvarchar(max)"); 65 | 66 | b.Property("ClaimValue") 67 | .HasColumnType("nvarchar(max)"); 68 | 69 | b.Property("RoleId") 70 | .IsRequired() 71 | .HasColumnType("nvarchar(450)"); 72 | 73 | b.HasKey("Id"); 74 | 75 | b.HasIndex("RoleId"); 76 | 77 | b.ToTable("AspNetRoleClaims", (string)null); 78 | }); 79 | 80 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => 81 | { 82 | b.Property("Id") 83 | .HasColumnType("nvarchar(450)"); 84 | 85 | b.Property("AccessFailedCount") 86 | .HasColumnType("int"); 87 | 88 | b.Property("ConcurrencyStamp") 89 | .IsConcurrencyToken() 90 | .HasColumnType("nvarchar(max)"); 91 | 92 | b.Property("Email") 93 | .HasMaxLength(256) 94 | .HasColumnType("nvarchar(256)"); 95 | 96 | b.Property("EmailConfirmed") 97 | .HasColumnType("bit"); 98 | 99 | b.Property("LockoutEnabled") 100 | .HasColumnType("bit"); 101 | 102 | b.Property("LockoutEnd") 103 | .HasColumnType("datetimeoffset"); 104 | 105 | b.Property("NormalizedEmail") 106 | .HasMaxLength(256) 107 | .HasColumnType("nvarchar(256)"); 108 | 109 | b.Property("NormalizedUserName") 110 | .HasMaxLength(256) 111 | .HasColumnType("nvarchar(256)"); 112 | 113 | b.Property("PasswordHash") 114 | .HasColumnType("nvarchar(max)"); 115 | 116 | b.Property("PhoneNumber") 117 | .HasColumnType("nvarchar(max)"); 118 | 119 | b.Property("PhoneNumberConfirmed") 120 | .HasColumnType("bit"); 121 | 122 | b.Property("SecurityStamp") 123 | .HasColumnType("nvarchar(max)"); 124 | 125 | b.Property("TwoFactorEnabled") 126 | .HasColumnType("bit"); 127 | 128 | b.Property("UserName") 129 | .HasMaxLength(256) 130 | .HasColumnType("nvarchar(256)"); 131 | 132 | b.HasKey("Id"); 133 | 134 | b.HasIndex("NormalizedEmail") 135 | .HasDatabaseName("EmailIndex"); 136 | 137 | b.HasIndex("NormalizedUserName") 138 | .IsUnique() 139 | .HasDatabaseName("UserNameIndex") 140 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 141 | 142 | b.ToTable("Users", (string)null); 143 | }); 144 | 145 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 146 | { 147 | b.Property("Id") 148 | .ValueGeneratedOnAdd() 149 | .HasColumnType("int"); 150 | 151 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 152 | 153 | b.Property("ClaimType") 154 | .HasColumnType("nvarchar(max)"); 155 | 156 | b.Property("ClaimValue") 157 | .HasColumnType("nvarchar(max)"); 158 | 159 | b.Property("UserId") 160 | .IsRequired() 161 | .HasColumnType("nvarchar(450)"); 162 | 163 | b.HasKey("Id"); 164 | 165 | b.HasIndex("UserId"); 166 | 167 | b.ToTable("AspNetUserClaims", (string)null); 168 | }); 169 | 170 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 171 | { 172 | b.Property("LoginProvider") 173 | .HasColumnType("nvarchar(450)"); 174 | 175 | b.Property("ProviderKey") 176 | .HasColumnType("nvarchar(450)"); 177 | 178 | b.Property("ProviderDisplayName") 179 | .HasColumnType("nvarchar(max)"); 180 | 181 | b.Property("UserId") 182 | .IsRequired() 183 | .HasColumnType("nvarchar(450)"); 184 | 185 | b.HasKey("LoginProvider", "ProviderKey"); 186 | 187 | b.HasIndex("UserId"); 188 | 189 | b.ToTable("AspNetUserLogins", (string)null); 190 | }); 191 | 192 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 193 | { 194 | b.Property("UserId") 195 | .HasColumnType("nvarchar(450)"); 196 | 197 | b.Property("RoleId") 198 | .HasColumnType("nvarchar(450)"); 199 | 200 | b.HasKey("UserId", "RoleId"); 201 | 202 | b.HasIndex("RoleId"); 203 | 204 | b.ToTable("AspNetUserRoles", (string)null); 205 | }); 206 | 207 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 208 | { 209 | b.Property("UserId") 210 | .HasColumnType("nvarchar(450)"); 211 | 212 | b.Property("LoginProvider") 213 | .HasColumnType("nvarchar(450)"); 214 | 215 | b.Property("Name") 216 | .HasColumnType("nvarchar(450)"); 217 | 218 | b.Property("Value") 219 | .HasColumnType("nvarchar(max)"); 220 | 221 | b.HasKey("UserId", "LoginProvider", "Name"); 222 | 223 | b.ToTable("AspNetUserTokens", (string)null); 224 | }); 225 | 226 | modelBuilder.Entity("Project.Core.Entities.General.Product", b => 227 | { 228 | b.Property("Id") 229 | .ValueGeneratedOnAdd() 230 | .HasColumnType("int"); 231 | 232 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 233 | 234 | b.Property("Code") 235 | .IsRequired() 236 | .HasMaxLength(8) 237 | .HasColumnType("nvarchar(8)"); 238 | 239 | b.Property("Description") 240 | .HasMaxLength(350) 241 | .HasColumnType("nvarchar(350)"); 242 | 243 | b.Property("EntryDate") 244 | .HasColumnType("datetime2"); 245 | 246 | b.Property("IsActive") 247 | .HasColumnType("bit"); 248 | 249 | b.Property("Name") 250 | .IsRequired() 251 | .HasMaxLength(100) 252 | .HasColumnType("nvarchar(100)"); 253 | 254 | b.Property("Price") 255 | .HasColumnType("real"); 256 | 257 | b.Property("UpdateDate") 258 | .HasColumnType("datetime2"); 259 | 260 | b.HasKey("Id"); 261 | 262 | b.ToTable("Products"); 263 | }); 264 | 265 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 266 | { 267 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 268 | .WithMany() 269 | .HasForeignKey("RoleId") 270 | .OnDelete(DeleteBehavior.Cascade) 271 | .IsRequired(); 272 | }); 273 | 274 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 275 | { 276 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 277 | .WithMany() 278 | .HasForeignKey("UserId") 279 | .OnDelete(DeleteBehavior.Cascade) 280 | .IsRequired(); 281 | }); 282 | 283 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 284 | { 285 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 286 | .WithMany() 287 | .HasForeignKey("UserId") 288 | .OnDelete(DeleteBehavior.Cascade) 289 | .IsRequired(); 290 | }); 291 | 292 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 293 | { 294 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 295 | .WithMany() 296 | .HasForeignKey("RoleId") 297 | .OnDelete(DeleteBehavior.Cascade) 298 | .IsRequired(); 299 | 300 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 301 | .WithMany() 302 | .HasForeignKey("UserId") 303 | .OnDelete(DeleteBehavior.Cascade) 304 | .IsRequired(); 305 | }); 306 | 307 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 308 | { 309 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 310 | .WithMany() 311 | .HasForeignKey("UserId") 312 | .OnDelete(DeleteBehavior.Cascade) 313 | .IsRequired(); 314 | }); 315 | #pragma warning restore 612, 618 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /Project.Infrastructure/Migrations/20230703085651_Initial_Migration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace Project.Infrastructure.Migrations 7 | { 8 | /// 9 | public partial class Initial_Migration : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.CreateTable( 15 | name: "Products", 16 | columns: table => new 17 | { 18 | Id = table.Column(type: "int", nullable: false) 19 | .Annotation("SqlServer:Identity", "1, 1"), 20 | Code = table.Column(type: "nvarchar(8)", maxLength: 8, nullable: false), 21 | Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), 22 | Price = table.Column(type: "real", nullable: false), 23 | Description = table.Column(type: "nvarchar(350)", maxLength: 350, nullable: true), 24 | IsActive = table.Column(type: "bit", nullable: false), 25 | EntryDate = table.Column(type: "datetime2", nullable: true), 26 | UpdateDate = table.Column(type: "datetime2", nullable: true) 27 | }, 28 | constraints: table => 29 | { 30 | table.PrimaryKey("PK_Products", x => x.Id); 31 | }); 32 | 33 | migrationBuilder.CreateTable( 34 | name: "Roles", 35 | columns: table => new 36 | { 37 | Id = table.Column(type: "nvarchar(450)", nullable: false), 38 | Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 39 | NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 40 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) 41 | }, 42 | constraints: table => 43 | { 44 | table.PrimaryKey("PK_Roles", x => x.Id); 45 | }); 46 | 47 | migrationBuilder.CreateTable( 48 | name: "Users", 49 | columns: table => new 50 | { 51 | Id = table.Column(type: "nvarchar(450)", nullable: false), 52 | UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 53 | NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 54 | Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 55 | NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 56 | EmailConfirmed = table.Column(type: "bit", nullable: false), 57 | PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), 58 | SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), 59 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), 60 | PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), 61 | PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), 62 | TwoFactorEnabled = table.Column(type: "bit", nullable: false), 63 | LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), 64 | LockoutEnabled = table.Column(type: "bit", nullable: false), 65 | AccessFailedCount = table.Column(type: "int", nullable: false) 66 | }, 67 | constraints: table => 68 | { 69 | table.PrimaryKey("PK_Users", x => x.Id); 70 | }); 71 | 72 | migrationBuilder.CreateTable( 73 | name: "AspNetRoleClaims", 74 | columns: table => new 75 | { 76 | Id = table.Column(type: "int", nullable: false) 77 | .Annotation("SqlServer:Identity", "1, 1"), 78 | RoleId = table.Column(type: "nvarchar(450)", nullable: false), 79 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 80 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) 81 | }, 82 | constraints: table => 83 | { 84 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 85 | table.ForeignKey( 86 | name: "FK_AspNetRoleClaims_Roles_RoleId", 87 | column: x => x.RoleId, 88 | principalTable: "Roles", 89 | principalColumn: "Id", 90 | onDelete: ReferentialAction.Cascade); 91 | }); 92 | 93 | migrationBuilder.CreateTable( 94 | name: "AspNetUserClaims", 95 | columns: table => new 96 | { 97 | Id = table.Column(type: "int", nullable: false) 98 | .Annotation("SqlServer:Identity", "1, 1"), 99 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 100 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 101 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) 102 | }, 103 | constraints: table => 104 | { 105 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 106 | table.ForeignKey( 107 | name: "FK_AspNetUserClaims_Users_UserId", 108 | column: x => x.UserId, 109 | principalTable: "Users", 110 | principalColumn: "Id", 111 | onDelete: ReferentialAction.Cascade); 112 | }); 113 | 114 | migrationBuilder.CreateTable( 115 | name: "AspNetUserLogins", 116 | columns: table => new 117 | { 118 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), 119 | ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), 120 | ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), 121 | UserId = table.Column(type: "nvarchar(450)", nullable: false) 122 | }, 123 | constraints: table => 124 | { 125 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 126 | table.ForeignKey( 127 | name: "FK_AspNetUserLogins_Users_UserId", 128 | column: x => x.UserId, 129 | principalTable: "Users", 130 | principalColumn: "Id", 131 | onDelete: ReferentialAction.Cascade); 132 | }); 133 | 134 | migrationBuilder.CreateTable( 135 | name: "AspNetUserRoles", 136 | columns: table => new 137 | { 138 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 139 | RoleId = table.Column(type: "nvarchar(450)", nullable: false) 140 | }, 141 | constraints: table => 142 | { 143 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 144 | table.ForeignKey( 145 | name: "FK_AspNetUserRoles_Roles_RoleId", 146 | column: x => x.RoleId, 147 | principalTable: "Roles", 148 | principalColumn: "Id", 149 | onDelete: ReferentialAction.Cascade); 150 | table.ForeignKey( 151 | name: "FK_AspNetUserRoles_Users_UserId", 152 | column: x => x.UserId, 153 | principalTable: "Users", 154 | principalColumn: "Id", 155 | onDelete: ReferentialAction.Cascade); 156 | }); 157 | 158 | migrationBuilder.CreateTable( 159 | name: "AspNetUserTokens", 160 | columns: table => new 161 | { 162 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 163 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), 164 | Name = table.Column(type: "nvarchar(450)", nullable: false), 165 | Value = table.Column(type: "nvarchar(max)", nullable: true) 166 | }, 167 | constraints: table => 168 | { 169 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 170 | table.ForeignKey( 171 | name: "FK_AspNetUserTokens_Users_UserId", 172 | column: x => x.UserId, 173 | principalTable: "Users", 174 | principalColumn: "Id", 175 | onDelete: ReferentialAction.Cascade); 176 | }); 177 | 178 | migrationBuilder.CreateIndex( 179 | name: "IX_AspNetRoleClaims_RoleId", 180 | table: "AspNetRoleClaims", 181 | column: "RoleId"); 182 | 183 | migrationBuilder.CreateIndex( 184 | name: "IX_AspNetUserClaims_UserId", 185 | table: "AspNetUserClaims", 186 | column: "UserId"); 187 | 188 | migrationBuilder.CreateIndex( 189 | name: "IX_AspNetUserLogins_UserId", 190 | table: "AspNetUserLogins", 191 | column: "UserId"); 192 | 193 | migrationBuilder.CreateIndex( 194 | name: "IX_AspNetUserRoles_RoleId", 195 | table: "AspNetUserRoles", 196 | column: "RoleId"); 197 | 198 | migrationBuilder.CreateIndex( 199 | name: "RoleNameIndex", 200 | table: "Roles", 201 | column: "NormalizedName", 202 | unique: true, 203 | filter: "[NormalizedName] IS NOT NULL"); 204 | 205 | migrationBuilder.CreateIndex( 206 | name: "EmailIndex", 207 | table: "Users", 208 | column: "NormalizedEmail"); 209 | 210 | migrationBuilder.CreateIndex( 211 | name: "UserNameIndex", 212 | table: "Users", 213 | column: "NormalizedUserName", 214 | unique: true, 215 | filter: "[NormalizedUserName] IS NOT NULL"); 216 | } 217 | 218 | /// 219 | protected override void Down(MigrationBuilder migrationBuilder) 220 | { 221 | migrationBuilder.DropTable( 222 | name: "AspNetRoleClaims"); 223 | 224 | migrationBuilder.DropTable( 225 | name: "AspNetUserClaims"); 226 | 227 | migrationBuilder.DropTable( 228 | name: "AspNetUserLogins"); 229 | 230 | migrationBuilder.DropTable( 231 | name: "AspNetUserRoles"); 232 | 233 | migrationBuilder.DropTable( 234 | name: "AspNetUserTokens"); 235 | 236 | migrationBuilder.DropTable( 237 | name: "Products"); 238 | 239 | migrationBuilder.DropTable( 240 | name: "Roles"); 241 | 242 | migrationBuilder.DropTable( 243 | name: "Users"); 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /Project.Infrastructure/Migrations/20230712112009_Seed Data.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | using Project.Infrastructure.Data; 9 | 10 | #nullable disable 11 | 12 | namespace Project.Infrastructure.Migrations 13 | { 14 | [DbContext(typeof(ApplicationDbContext))] 15 | [Migration("20230712112009_Seed Data")] 16 | partial class SeedData 17 | { 18 | /// 19 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 | { 21 | #pragma warning disable 612, 618 22 | modelBuilder 23 | .HasAnnotation("ProductVersion", "7.0.8") 24 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 25 | 26 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 27 | 28 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 29 | { 30 | b.Property("Id") 31 | .HasColumnType("nvarchar(450)"); 32 | 33 | b.Property("ConcurrencyStamp") 34 | .IsConcurrencyToken() 35 | .HasColumnType("nvarchar(max)"); 36 | 37 | b.Property("Name") 38 | .HasMaxLength(256) 39 | .HasColumnType("nvarchar(256)"); 40 | 41 | b.Property("NormalizedName") 42 | .HasMaxLength(256) 43 | .HasColumnType("nvarchar(256)"); 44 | 45 | b.HasKey("Id"); 46 | 47 | b.HasIndex("NormalizedName") 48 | .IsUnique() 49 | .HasDatabaseName("RoleNameIndex") 50 | .HasFilter("[NormalizedName] IS NOT NULL"); 51 | 52 | b.ToTable("Roles", (string)null); 53 | }); 54 | 55 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 56 | { 57 | b.Property("Id") 58 | .ValueGeneratedOnAdd() 59 | .HasColumnType("int"); 60 | 61 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 62 | 63 | b.Property("ClaimType") 64 | .HasColumnType("nvarchar(max)"); 65 | 66 | b.Property("ClaimValue") 67 | .HasColumnType("nvarchar(max)"); 68 | 69 | b.Property("RoleId") 70 | .IsRequired() 71 | .HasColumnType("nvarchar(450)"); 72 | 73 | b.HasKey("Id"); 74 | 75 | b.HasIndex("RoleId"); 76 | 77 | b.ToTable("AspNetRoleClaims", (string)null); 78 | }); 79 | 80 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => 81 | { 82 | b.Property("Id") 83 | .HasColumnType("nvarchar(450)"); 84 | 85 | b.Property("AccessFailedCount") 86 | .HasColumnType("int"); 87 | 88 | b.Property("ConcurrencyStamp") 89 | .IsConcurrencyToken() 90 | .HasColumnType("nvarchar(max)"); 91 | 92 | b.Property("Email") 93 | .HasMaxLength(256) 94 | .HasColumnType("nvarchar(256)"); 95 | 96 | b.Property("EmailConfirmed") 97 | .HasColumnType("bit"); 98 | 99 | b.Property("LockoutEnabled") 100 | .HasColumnType("bit"); 101 | 102 | b.Property("LockoutEnd") 103 | .HasColumnType("datetimeoffset"); 104 | 105 | b.Property("NormalizedEmail") 106 | .HasMaxLength(256) 107 | .HasColumnType("nvarchar(256)"); 108 | 109 | b.Property("NormalizedUserName") 110 | .HasMaxLength(256) 111 | .HasColumnType("nvarchar(256)"); 112 | 113 | b.Property("PasswordHash") 114 | .HasColumnType("nvarchar(max)"); 115 | 116 | b.Property("PhoneNumber") 117 | .HasColumnType("nvarchar(max)"); 118 | 119 | b.Property("PhoneNumberConfirmed") 120 | .HasColumnType("bit"); 121 | 122 | b.Property("SecurityStamp") 123 | .HasColumnType("nvarchar(max)"); 124 | 125 | b.Property("TwoFactorEnabled") 126 | .HasColumnType("bit"); 127 | 128 | b.Property("UserName") 129 | .HasMaxLength(256) 130 | .HasColumnType("nvarchar(256)"); 131 | 132 | b.HasKey("Id"); 133 | 134 | b.HasIndex("NormalizedEmail") 135 | .HasDatabaseName("EmailIndex"); 136 | 137 | b.HasIndex("NormalizedUserName") 138 | .IsUnique() 139 | .HasDatabaseName("UserNameIndex") 140 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 141 | 142 | b.ToTable("Users", (string)null); 143 | }); 144 | 145 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 146 | { 147 | b.Property("Id") 148 | .ValueGeneratedOnAdd() 149 | .HasColumnType("int"); 150 | 151 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 152 | 153 | b.Property("ClaimType") 154 | .HasColumnType("nvarchar(max)"); 155 | 156 | b.Property("ClaimValue") 157 | .HasColumnType("nvarchar(max)"); 158 | 159 | b.Property("UserId") 160 | .IsRequired() 161 | .HasColumnType("nvarchar(450)"); 162 | 163 | b.HasKey("Id"); 164 | 165 | b.HasIndex("UserId"); 166 | 167 | b.ToTable("AspNetUserClaims", (string)null); 168 | }); 169 | 170 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 171 | { 172 | b.Property("LoginProvider") 173 | .HasColumnType("nvarchar(450)"); 174 | 175 | b.Property("ProviderKey") 176 | .HasColumnType("nvarchar(450)"); 177 | 178 | b.Property("ProviderDisplayName") 179 | .HasColumnType("nvarchar(max)"); 180 | 181 | b.Property("UserId") 182 | .IsRequired() 183 | .HasColumnType("nvarchar(450)"); 184 | 185 | b.HasKey("LoginProvider", "ProviderKey"); 186 | 187 | b.HasIndex("UserId"); 188 | 189 | b.ToTable("AspNetUserLogins", (string)null); 190 | }); 191 | 192 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 193 | { 194 | b.Property("UserId") 195 | .HasColumnType("nvarchar(450)"); 196 | 197 | b.Property("RoleId") 198 | .HasColumnType("nvarchar(450)"); 199 | 200 | b.HasKey("UserId", "RoleId"); 201 | 202 | b.HasIndex("RoleId"); 203 | 204 | b.ToTable("AspNetUserRoles", (string)null); 205 | }); 206 | 207 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 208 | { 209 | b.Property("UserId") 210 | .HasColumnType("nvarchar(450)"); 211 | 212 | b.Property("LoginProvider") 213 | .HasColumnType("nvarchar(450)"); 214 | 215 | b.Property("Name") 216 | .HasColumnType("nvarchar(450)"); 217 | 218 | b.Property("Value") 219 | .HasColumnType("nvarchar(max)"); 220 | 221 | b.HasKey("UserId", "LoginProvider", "Name"); 222 | 223 | b.ToTable("AspNetUserTokens", (string)null); 224 | }); 225 | 226 | modelBuilder.Entity("Project.Core.Entities.General.Product", b => 227 | { 228 | b.Property("Id") 229 | .ValueGeneratedOnAdd() 230 | .HasColumnType("int"); 231 | 232 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 233 | 234 | b.Property("Code") 235 | .IsRequired() 236 | .HasMaxLength(8) 237 | .HasColumnType("nvarchar(8)"); 238 | 239 | b.Property("Description") 240 | .HasMaxLength(350) 241 | .HasColumnType("nvarchar(350)"); 242 | 243 | b.Property("EntryDate") 244 | .HasColumnType("datetime2"); 245 | 246 | b.Property("IsActive") 247 | .HasColumnType("bit"); 248 | 249 | b.Property("Name") 250 | .IsRequired() 251 | .HasMaxLength(100) 252 | .HasColumnType("nvarchar(100)"); 253 | 254 | b.Property("Price") 255 | .HasColumnType("real"); 256 | 257 | b.Property("UpdateDate") 258 | .HasColumnType("datetime2"); 259 | 260 | b.HasKey("Id"); 261 | 262 | b.ToTable("Products"); 263 | 264 | b.HasData( 265 | new 266 | { 267 | Id = 1, 268 | Code = "P001", 269 | IsActive = true, 270 | Name = "Product 1", 271 | Price = 9.99f 272 | }, 273 | new 274 | { 275 | Id = 2, 276 | Code = "P002", 277 | IsActive = true, 278 | Name = "Product 2", 279 | Price = 12f 280 | }, 281 | new 282 | { 283 | Id = 3, 284 | Code = "P003", 285 | IsActive = true, 286 | Name = "Product 3", 287 | Price = 13f 288 | }, 289 | new 290 | { 291 | Id = 4, 292 | Code = "P004", 293 | IsActive = true, 294 | Name = "Product 4", 295 | Price = 14f 296 | }, 297 | new 298 | { 299 | Id = 5, 300 | Code = "P005", 301 | IsActive = true, 302 | Name = "Product 5", 303 | Price = 15f 304 | }, 305 | new 306 | { 307 | Id = 6, 308 | Code = "P006", 309 | IsActive = true, 310 | Name = "Product 6", 311 | Price = 16f 312 | }, 313 | new 314 | { 315 | Id = 7, 316 | Code = "P007", 317 | IsActive = true, 318 | Name = "Product 7", 319 | Price = 17f 320 | }, 321 | new 322 | { 323 | Id = 8, 324 | Code = "P008", 325 | IsActive = true, 326 | Name = "Product 8", 327 | Price = 18f 328 | }, 329 | new 330 | { 331 | Id = 9, 332 | Code = "P009", 333 | IsActive = true, 334 | Name = "Product 9", 335 | Price = 19f 336 | }, 337 | new 338 | { 339 | Id = 10, 340 | Code = "P010", 341 | IsActive = true, 342 | Name = "Product 10", 343 | Price = 19.99f 344 | }); 345 | }); 346 | 347 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 348 | { 349 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 350 | .WithMany() 351 | .HasForeignKey("RoleId") 352 | .OnDelete(DeleteBehavior.Cascade) 353 | .IsRequired(); 354 | }); 355 | 356 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 357 | { 358 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 359 | .WithMany() 360 | .HasForeignKey("UserId") 361 | .OnDelete(DeleteBehavior.Cascade) 362 | .IsRequired(); 363 | }); 364 | 365 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 366 | { 367 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 368 | .WithMany() 369 | .HasForeignKey("UserId") 370 | .OnDelete(DeleteBehavior.Cascade) 371 | .IsRequired(); 372 | }); 373 | 374 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 375 | { 376 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 377 | .WithMany() 378 | .HasForeignKey("RoleId") 379 | .OnDelete(DeleteBehavior.Cascade) 380 | .IsRequired(); 381 | 382 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 383 | .WithMany() 384 | .HasForeignKey("UserId") 385 | .OnDelete(DeleteBehavior.Cascade) 386 | .IsRequired(); 387 | }); 388 | 389 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 390 | { 391 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 392 | .WithMany() 393 | .HasForeignKey("UserId") 394 | .OnDelete(DeleteBehavior.Cascade) 395 | .IsRequired(); 396 | }); 397 | #pragma warning restore 612, 618 398 | } 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /Project.Infrastructure/Migrations/20230712112009_Seed Data.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | #pragma warning disable CA1814 // Prefer jagged arrays over multidimensional 6 | 7 | namespace Project.Infrastructure.Migrations 8 | { 9 | /// 10 | public partial class SeedData : Migration 11 | { 12 | /// 13 | protected override void Up(MigrationBuilder migrationBuilder) 14 | { 15 | migrationBuilder.InsertData( 16 | table: "Products", 17 | columns: new[] { "Id", "Code", "Description", "EntryDate", "IsActive", "Name", "Price", "UpdateDate" }, 18 | values: new object[,] 19 | { 20 | { 1, "P001", null, null, true, "Product 1", 9.99f, null }, 21 | { 2, "P002", null, null, true, "Product 2", 12f, null }, 22 | { 3, "P003", null, null, true, "Product 3", 13f, null }, 23 | { 4, "P004", null, null, true, "Product 4", 14f, null }, 24 | { 5, "P005", null, null, true, "Product 5", 15f, null }, 25 | { 6, "P006", null, null, true, "Product 6", 16f, null }, 26 | { 7, "P007", null, null, true, "Product 7", 17f, null }, 27 | { 8, "P008", null, null, true, "Product 8", 18f, null }, 28 | { 9, "P009", null, null, true, "Product 9", 19f, null }, 29 | { 10, "P010", null, null, true, "Product 10", 19.99f, null } 30 | }); 31 | } 32 | 33 | /// 34 | protected override void Down(MigrationBuilder migrationBuilder) 35 | { 36 | migrationBuilder.DeleteData( 37 | table: "Products", 38 | keyColumn: "Id", 39 | keyValue: 1); 40 | 41 | migrationBuilder.DeleteData( 42 | table: "Products", 43 | keyColumn: "Id", 44 | keyValue: 2); 45 | 46 | migrationBuilder.DeleteData( 47 | table: "Products", 48 | keyColumn: "Id", 49 | keyValue: 3); 50 | 51 | migrationBuilder.DeleteData( 52 | table: "Products", 53 | keyColumn: "Id", 54 | keyValue: 4); 55 | 56 | migrationBuilder.DeleteData( 57 | table: "Products", 58 | keyColumn: "Id", 59 | keyValue: 5); 60 | 61 | migrationBuilder.DeleteData( 62 | table: "Products", 63 | keyColumn: "Id", 64 | keyValue: 6); 65 | 66 | migrationBuilder.DeleteData( 67 | table: "Products", 68 | keyColumn: "Id", 69 | keyValue: 7); 70 | 71 | migrationBuilder.DeleteData( 72 | table: "Products", 73 | keyColumn: "Id", 74 | keyValue: 8); 75 | 76 | migrationBuilder.DeleteData( 77 | table: "Products", 78 | keyColumn: "Id", 79 | keyValue: 9); 80 | 81 | migrationBuilder.DeleteData( 82 | table: "Products", 83 | keyColumn: "Id", 84 | keyValue: 10); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Project.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | using Project.Infrastructure.Data; 8 | 9 | #nullable disable 10 | 11 | namespace Project.Infrastructure.Migrations 12 | { 13 | [DbContext(typeof(ApplicationDbContext))] 14 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "7.0.8") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 22 | 23 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 24 | 25 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 26 | { 27 | b.Property("Id") 28 | .HasColumnType("nvarchar(450)"); 29 | 30 | b.Property("ConcurrencyStamp") 31 | .IsConcurrencyToken() 32 | .HasColumnType("nvarchar(max)"); 33 | 34 | b.Property("Name") 35 | .HasMaxLength(256) 36 | .HasColumnType("nvarchar(256)"); 37 | 38 | b.Property("NormalizedName") 39 | .HasMaxLength(256) 40 | .HasColumnType("nvarchar(256)"); 41 | 42 | b.HasKey("Id"); 43 | 44 | b.HasIndex("NormalizedName") 45 | .IsUnique() 46 | .HasDatabaseName("RoleNameIndex") 47 | .HasFilter("[NormalizedName] IS NOT NULL"); 48 | 49 | b.ToTable("Roles", (string)null); 50 | }); 51 | 52 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 53 | { 54 | b.Property("Id") 55 | .ValueGeneratedOnAdd() 56 | .HasColumnType("int"); 57 | 58 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 59 | 60 | b.Property("ClaimType") 61 | .HasColumnType("nvarchar(max)"); 62 | 63 | b.Property("ClaimValue") 64 | .HasColumnType("nvarchar(max)"); 65 | 66 | b.Property("RoleId") 67 | .IsRequired() 68 | .HasColumnType("nvarchar(450)"); 69 | 70 | b.HasKey("Id"); 71 | 72 | b.HasIndex("RoleId"); 73 | 74 | b.ToTable("AspNetRoleClaims", (string)null); 75 | }); 76 | 77 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => 78 | { 79 | b.Property("Id") 80 | .HasColumnType("nvarchar(450)"); 81 | 82 | b.Property("AccessFailedCount") 83 | .HasColumnType("int"); 84 | 85 | b.Property("ConcurrencyStamp") 86 | .IsConcurrencyToken() 87 | .HasColumnType("nvarchar(max)"); 88 | 89 | b.Property("Email") 90 | .HasMaxLength(256) 91 | .HasColumnType("nvarchar(256)"); 92 | 93 | b.Property("EmailConfirmed") 94 | .HasColumnType("bit"); 95 | 96 | b.Property("LockoutEnabled") 97 | .HasColumnType("bit"); 98 | 99 | b.Property("LockoutEnd") 100 | .HasColumnType("datetimeoffset"); 101 | 102 | b.Property("NormalizedEmail") 103 | .HasMaxLength(256) 104 | .HasColumnType("nvarchar(256)"); 105 | 106 | b.Property("NormalizedUserName") 107 | .HasMaxLength(256) 108 | .HasColumnType("nvarchar(256)"); 109 | 110 | b.Property("PasswordHash") 111 | .HasColumnType("nvarchar(max)"); 112 | 113 | b.Property("PhoneNumber") 114 | .HasColumnType("nvarchar(max)"); 115 | 116 | b.Property("PhoneNumberConfirmed") 117 | .HasColumnType("bit"); 118 | 119 | b.Property("SecurityStamp") 120 | .HasColumnType("nvarchar(max)"); 121 | 122 | b.Property("TwoFactorEnabled") 123 | .HasColumnType("bit"); 124 | 125 | b.Property("UserName") 126 | .HasMaxLength(256) 127 | .HasColumnType("nvarchar(256)"); 128 | 129 | b.HasKey("Id"); 130 | 131 | b.HasIndex("NormalizedEmail") 132 | .HasDatabaseName("EmailIndex"); 133 | 134 | b.HasIndex("NormalizedUserName") 135 | .IsUnique() 136 | .HasDatabaseName("UserNameIndex") 137 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 138 | 139 | b.ToTable("Users", (string)null); 140 | }); 141 | 142 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 143 | { 144 | b.Property("Id") 145 | .ValueGeneratedOnAdd() 146 | .HasColumnType("int"); 147 | 148 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 149 | 150 | b.Property("ClaimType") 151 | .HasColumnType("nvarchar(max)"); 152 | 153 | b.Property("ClaimValue") 154 | .HasColumnType("nvarchar(max)"); 155 | 156 | b.Property("UserId") 157 | .IsRequired() 158 | .HasColumnType("nvarchar(450)"); 159 | 160 | b.HasKey("Id"); 161 | 162 | b.HasIndex("UserId"); 163 | 164 | b.ToTable("AspNetUserClaims", (string)null); 165 | }); 166 | 167 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 168 | { 169 | b.Property("LoginProvider") 170 | .HasColumnType("nvarchar(450)"); 171 | 172 | b.Property("ProviderKey") 173 | .HasColumnType("nvarchar(450)"); 174 | 175 | b.Property("ProviderDisplayName") 176 | .HasColumnType("nvarchar(max)"); 177 | 178 | b.Property("UserId") 179 | .IsRequired() 180 | .HasColumnType("nvarchar(450)"); 181 | 182 | b.HasKey("LoginProvider", "ProviderKey"); 183 | 184 | b.HasIndex("UserId"); 185 | 186 | b.ToTable("AspNetUserLogins", (string)null); 187 | }); 188 | 189 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 190 | { 191 | b.Property("UserId") 192 | .HasColumnType("nvarchar(450)"); 193 | 194 | b.Property("RoleId") 195 | .HasColumnType("nvarchar(450)"); 196 | 197 | b.HasKey("UserId", "RoleId"); 198 | 199 | b.HasIndex("RoleId"); 200 | 201 | b.ToTable("AspNetUserRoles", (string)null); 202 | }); 203 | 204 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 205 | { 206 | b.Property("UserId") 207 | .HasColumnType("nvarchar(450)"); 208 | 209 | b.Property("LoginProvider") 210 | .HasColumnType("nvarchar(450)"); 211 | 212 | b.Property("Name") 213 | .HasColumnType("nvarchar(450)"); 214 | 215 | b.Property("Value") 216 | .HasColumnType("nvarchar(max)"); 217 | 218 | b.HasKey("UserId", "LoginProvider", "Name"); 219 | 220 | b.ToTable("AspNetUserTokens", (string)null); 221 | }); 222 | 223 | modelBuilder.Entity("Project.Core.Entities.General.Product", b => 224 | { 225 | b.Property("Id") 226 | .ValueGeneratedOnAdd() 227 | .HasColumnType("int"); 228 | 229 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 230 | 231 | b.Property("Code") 232 | .IsRequired() 233 | .HasMaxLength(8) 234 | .HasColumnType("nvarchar(8)"); 235 | 236 | b.Property("Description") 237 | .HasMaxLength(350) 238 | .HasColumnType("nvarchar(350)"); 239 | 240 | b.Property("EntryDate") 241 | .HasColumnType("datetime2"); 242 | 243 | b.Property("IsActive") 244 | .HasColumnType("bit"); 245 | 246 | b.Property("Name") 247 | .IsRequired() 248 | .HasMaxLength(100) 249 | .HasColumnType("nvarchar(100)"); 250 | 251 | b.Property("Price") 252 | .HasColumnType("real"); 253 | 254 | b.Property("UpdateDate") 255 | .HasColumnType("datetime2"); 256 | 257 | b.HasKey("Id"); 258 | 259 | b.ToTable("Products"); 260 | 261 | b.HasData( 262 | new 263 | { 264 | Id = 1, 265 | Code = "P001", 266 | IsActive = true, 267 | Name = "Product 1", 268 | Price = 9.99f 269 | }, 270 | new 271 | { 272 | Id = 2, 273 | Code = "P002", 274 | IsActive = true, 275 | Name = "Product 2", 276 | Price = 12f 277 | }, 278 | new 279 | { 280 | Id = 3, 281 | Code = "P003", 282 | IsActive = true, 283 | Name = "Product 3", 284 | Price = 13f 285 | }, 286 | new 287 | { 288 | Id = 4, 289 | Code = "P004", 290 | IsActive = true, 291 | Name = "Product 4", 292 | Price = 14f 293 | }, 294 | new 295 | { 296 | Id = 5, 297 | Code = "P005", 298 | IsActive = true, 299 | Name = "Product 5", 300 | Price = 15f 301 | }, 302 | new 303 | { 304 | Id = 6, 305 | Code = "P006", 306 | IsActive = true, 307 | Name = "Product 6", 308 | Price = 16f 309 | }, 310 | new 311 | { 312 | Id = 7, 313 | Code = "P007", 314 | IsActive = true, 315 | Name = "Product 7", 316 | Price = 17f 317 | }, 318 | new 319 | { 320 | Id = 8, 321 | Code = "P008", 322 | IsActive = true, 323 | Name = "Product 8", 324 | Price = 18f 325 | }, 326 | new 327 | { 328 | Id = 9, 329 | Code = "P009", 330 | IsActive = true, 331 | Name = "Product 9", 332 | Price = 19f 333 | }, 334 | new 335 | { 336 | Id = 10, 337 | Code = "P010", 338 | IsActive = true, 339 | Name = "Product 10", 340 | Price = 19.99f 341 | }); 342 | }); 343 | 344 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 345 | { 346 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 347 | .WithMany() 348 | .HasForeignKey("RoleId") 349 | .OnDelete(DeleteBehavior.Cascade) 350 | .IsRequired(); 351 | }); 352 | 353 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 354 | { 355 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 356 | .WithMany() 357 | .HasForeignKey("UserId") 358 | .OnDelete(DeleteBehavior.Cascade) 359 | .IsRequired(); 360 | }); 361 | 362 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 363 | { 364 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 365 | .WithMany() 366 | .HasForeignKey("UserId") 367 | .OnDelete(DeleteBehavior.Cascade) 368 | .IsRequired(); 369 | }); 370 | 371 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 372 | { 373 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) 374 | .WithMany() 375 | .HasForeignKey("RoleId") 376 | .OnDelete(DeleteBehavior.Cascade) 377 | .IsRequired(); 378 | 379 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 380 | .WithMany() 381 | .HasForeignKey("UserId") 382 | .OnDelete(DeleteBehavior.Cascade) 383 | .IsRequired(); 384 | }); 385 | 386 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 387 | { 388 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) 389 | .WithMany() 390 | .HasForeignKey("UserId") 391 | .OnDelete(DeleteBehavior.Cascade) 392 | .IsRequired(); 393 | }); 394 | #pragma warning restore 612, 618 395 | } 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /Project.Infrastructure/Project.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /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 | private 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 | return await _dbContext.Set().AsNoTracking().ToListAsync(); 24 | } 25 | 26 | public async Task> GetPaginatedData(int pageNumber, int pageSize) 27 | { 28 | var query = _dbContext.Set() 29 | .Skip((pageNumber - 1) * pageSize) 30 | .Take(pageSize) 31 | .AsNoTracking(); 32 | 33 | var data = await query.ToListAsync(); 34 | var totalCount = await _dbContext.Set().CountAsync(); 35 | 36 | return new PaginatedDataViewModel(data, totalCount); 37 | } 38 | 39 | public async Task GetById(Tid id) 40 | { 41 | var data = await _dbContext.Set().FindAsync(id); 42 | if (data == null) 43 | throw new NotFoundException("No data found"); 44 | return data; 45 | } 46 | 47 | public async Task IsExists(string key, Tvalue value) 48 | { 49 | var parameter = Expression.Parameter(typeof(T), "x"); 50 | var property = Expression.Property(parameter, key); 51 | var constant = Expression.Constant(value); 52 | var equality = Expression.Equal(property, constant); 53 | var lambda = Expression.Lambda>(equality, parameter); 54 | 55 | return await _dbContext.Set().AnyAsync(lambda); 56 | } 57 | 58 | //Before update existence check 59 | public async Task IsExistsForUpdate(Tid id, string key, string value) 60 | { 61 | var parameter = Expression.Parameter(typeof(T), "x"); 62 | var property = Expression.Property(parameter, key); 63 | var constant = Expression.Constant(value); 64 | var equality = Expression.Equal(property, constant); 65 | 66 | var idProperty = Expression.Property(parameter, "Id"); 67 | var idEquality = Expression.NotEqual(idProperty, Expression.Constant(id)); 68 | 69 | var combinedExpression = Expression.AndAlso(equality, idEquality); 70 | var lambda = Expression.Lambda>(combinedExpression, parameter); 71 | 72 | return await _dbContext.Set().AnyAsync(lambda); 73 | } 74 | 75 | 76 | public async Task Create(T model) 77 | { 78 | await _dbContext.Set().AddAsync(model); 79 | await _dbContext.SaveChangesAsync(); 80 | return model; 81 | } 82 | 83 | public async Task Update(T model) 84 | { 85 | _dbContext.Set().Update(model); 86 | await _dbContext.SaveChangesAsync(); 87 | } 88 | 89 | public async Task Delete(T model) 90 | { 91 | _dbContext.Set().Remove(model); 92 | await _dbContext.SaveChangesAsync(); 93 | } 94 | 95 | public async Task SaveChangeAsync() 96 | { 97 | await _dbContext.SaveChangesAsync(); 98 | } 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/ProductRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using Project.Core.Interfaces.IRepositories; 3 | using Project.Infrastructure.Data; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Project.Infrastructure.Repositories 11 | { 12 | public class ProductRepository : BaseRepository, IProductRepository 13 | { 14 | public ProductRepository(ApplicationDbContext dbContext) : base(dbContext) 15 | { 16 | } 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Project.UI/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace Project.UI.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | //Dashboard Page 8 | public IActionResult Index() 9 | { 10 | return View(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Project.UI/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Caching.Memory; 3 | using Project.Core.Entities.General; 4 | using Project.Core.Interfaces.IServices; 5 | using X.PagedList; 6 | 7 | namespace Project.UI.Controllers 8 | { 9 | public class ProductController : Controller 10 | { 11 | private readonly ILogger _logger; 12 | private readonly IProductService _productService; 13 | private readonly IMemoryCache _memoryCache; 14 | 15 | public ProductController(ILogger logger, IProductService productService, IMemoryCache memoryCache) 16 | { 17 | _logger = logger; 18 | _productService = productService; 19 | _memoryCache = memoryCache; 20 | } 21 | 22 | // GET: ProductController 23 | public async Task Index(int? page) 24 | { 25 | try 26 | { 27 | int pageSize = 4; 28 | int pageNumber = (page ?? 1); 29 | 30 | //Get peginated data 31 | var products = await _productService.GetPaginatedProducts(pageNumber, pageSize); 32 | 33 | // Convert the list of products to an instance of StaticPagedList> 34 | var pagedProducts = new StaticPagedList(products.Data, pageNumber, pageSize, products.TotalCount); 35 | 36 | return View(pagedProducts); 37 | } 38 | catch (Exception ex) 39 | { 40 | _logger.LogError(ex, "An error occurred while retrieving products"); 41 | return StatusCode(500, ex.Message); 42 | } 43 | 44 | } 45 | 46 | //GET: ProductController 47 | public async Task IndexWithoutPagination() 48 | { 49 | try 50 | { 51 | var products = await _productService.GetProducts(); 52 | return View(products); 53 | } 54 | catch (Exception ex) 55 | { 56 | _logger.LogError(ex, "An error occurred while retrieving products"); 57 | return StatusCode(500, ex.Message); 58 | } 59 | 60 | } 61 | 62 | // GET: ProductController/Details/5 63 | public async Task Details(int id) 64 | { 65 | try 66 | { 67 | var product = new ProductViewModel(); 68 | 69 | // Attempt to retrieve the product from the cache 70 | if (_memoryCache.TryGetValue($"Product_{id}", out ProductViewModel cachedProduct)) 71 | { 72 | product = cachedProduct; 73 | } 74 | else 75 | { 76 | // If not found in cache, fetch the product from the data source 77 | product = await _productService.GetProduct(id); 78 | 79 | if (product != null) 80 | { 81 | // Cache the product with an expiration time of 10 minutes 82 | _memoryCache.Set($"Product_{id}", product, TimeSpan.FromMinutes(10)); 83 | } 84 | } 85 | 86 | return View(product); 87 | } 88 | catch (Exception ex) 89 | { 90 | _logger.LogError(ex, $"An error occurred while retrieving the product"); 91 | return StatusCode(500, ex.Message); 92 | } 93 | } 94 | 95 | // GET: ProductController/Create 96 | public ActionResult Create() 97 | { 98 | return View(); 99 | } 100 | 101 | // POST: ProductController/Create 102 | [HttpPost] 103 | public async Task Create(ProductViewModel model) 104 | { 105 | if (ModelState.IsValid) 106 | { 107 | if (await _productService.IsExists("Name", model.Name)) 108 | { 109 | ModelState.AddModelError("Exists", $"The product name- '{model.Name}' already exists"); 110 | return View(model); 111 | } 112 | 113 | if (await _productService.IsExists("Code", model.Code)) 114 | { 115 | ModelState.AddModelError("Exists", $"The product code- '{model.Code}' already exists"); 116 | return View(model); 117 | } 118 | 119 | try 120 | { 121 | var data = await _productService.Create(model); 122 | return RedirectToAction("Index"); 123 | } 124 | catch (Exception ex) 125 | { 126 | _logger.LogError(ex, $"An error occurred while adding the product"); 127 | ModelState.AddModelError("Error", $"An error occurred while adding the product- " + ex.Message); 128 | return View(model); 129 | } 130 | } 131 | return View(model); 132 | } 133 | 134 | // GET: ProductController/Edit/5 135 | public async Task Edit(int id) 136 | { 137 | try 138 | { 139 | var product = new ProductViewModel(); 140 | 141 | // Attempt to retrieve the product from the cache 142 | if (_memoryCache.TryGetValue($"Product_{id}", out ProductViewModel cachedProduct)) 143 | { 144 | product = cachedProduct; 145 | } 146 | else 147 | { 148 | // If not found in cache, fetch the product from the data source 149 | product = await _productService.GetProduct(id); 150 | 151 | if (product != null) 152 | { 153 | // Cache the product with an expiration time of 10 minutes 154 | _memoryCache.Set($"Product_{id}", product, TimeSpan.FromMinutes(10)); 155 | } 156 | } 157 | 158 | return View(product); 159 | } 160 | catch (Exception ex) 161 | { 162 | _logger.LogError(ex, $"An error occurred while retrieving the product"); 163 | return StatusCode(500, ex.Message); 164 | } 165 | } 166 | 167 | // POST: ProductController/Edit/5 168 | [HttpPost] 169 | public async Task Edit(ProductViewModel model) 170 | { 171 | if (ModelState.IsValid) 172 | { 173 | if (await _productService.IsExistsForUpdate(model.Id, "Name", model.Name)) 174 | { 175 | ModelState.AddModelError("Exists", $"The product name- '{model.Name}' already exists"); 176 | return View(model); 177 | } 178 | 179 | if (await _productService.IsExistsForUpdate(model.Id, "Code", model.Code)) 180 | { 181 | ModelState.AddModelError("Exists", $"The product code- '{model.Code}' already exists"); 182 | return View(model); 183 | } 184 | 185 | try 186 | { 187 | await _productService.Update(model); 188 | 189 | // Remove data from cache by key 190 | _memoryCache.Remove($"Product_{model.Id}"); 191 | 192 | return RedirectToAction("Index"); 193 | } 194 | catch (Exception ex) 195 | { 196 | _logger.LogError(ex, $"An error occurred while updating the product"); 197 | ModelState.AddModelError("Error", $"An error occurred while updating the product- " + ex.Message); 198 | return View(model); 199 | } 200 | } 201 | return View(model); 202 | } 203 | 204 | // Get: ProductController/Delete/5 205 | public async Task Delete(int id) 206 | { 207 | try 208 | { 209 | await _productService.Delete(id); 210 | 211 | // Remove data from cache by key 212 | _memoryCache.Remove($"Product_{id}"); 213 | 214 | return RedirectToAction("Index"); 215 | } 216 | catch (Exception ex) 217 | { 218 | _logger.LogError(ex, "An error occurred while deleting the product"); 219 | return View("Error"); 220 | } 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /Project.UI/Extensions/ServiceExtension.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Project.Core.Entities.General; 4 | using Project.Core.Interfaces.IMapper; 5 | using Project.Core.Interfaces.IRepositories; 6 | using Project.Core.Interfaces.IServices; 7 | using Project.Core.Mapper; 8 | using Project.Core.Services; 9 | using Project.Infrastructure.Repositories; 10 | 11 | namespace Project.UI.Extensions 12 | { 13 | public static class ServiceExtension 14 | { 15 | public static IServiceCollection RegisterService(this IServiceCollection services) 16 | { 17 | #region Services 18 | services.AddScoped(); 19 | 20 | #endregion 21 | 22 | #region Repositories 23 | services.AddTransient(); 24 | 25 | #endregion 26 | 27 | #region Mapper 28 | var configuration = new MapperConfiguration(cfg => 29 | { 30 | cfg.CreateMap(); 31 | cfg.CreateMap(); 32 | }); 33 | 34 | IMapper mapper = configuration.CreateMapper(); 35 | 36 | // Register the IMapperService implementation with your dependency injection container 37 | services.AddSingleton>(new BaseMapper(mapper)); 38 | services.AddSingleton>(new BaseMapper(mapper)); 39 | 40 | #endregion 41 | 42 | return services; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Project.UI/Middlewares/RequestResponseLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace Project.UI.Middlewares 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | public class RequestResponseLoggingMiddleware 9 | { 10 | private readonly RequestDelegate _next; 11 | private readonly ILogger _logger; 12 | 13 | public RequestResponseLoggingMiddleware(RequestDelegate next, ILogger logger) 14 | { 15 | _next = next; 16 | _logger = logger; 17 | } 18 | 19 | public async Task Invoke(HttpContext context) 20 | { 21 | // Log the incoming request 22 | LogRequest(context.Request); 23 | 24 | 25 | // Call the next middleware in the pipeline 26 | await _next(context); 27 | 28 | // Log the outgoing response 29 | LogResponse(context.Response); 30 | } 31 | 32 | private void LogRequest(HttpRequest request) 33 | { 34 | _logger.LogInformation($"Request received: {request.Method} {request.Path}"); 35 | _logger.LogInformation($"Request headers: {GetHeadersAsString(request.Headers)}"); 36 | } 37 | 38 | private void LogResponse(HttpResponse response) 39 | { 40 | _logger.LogInformation($"Response sent: {response.StatusCode}"); 41 | _logger.LogInformation($"Response headers: {GetHeadersAsString(response.Headers)}"); 42 | } 43 | 44 | private string GetHeadersAsString(IHeaderDictionary headers) 45 | { 46 | var stringBuilder = new StringBuilder(); 47 | foreach (var (key, value) in headers) 48 | { 49 | stringBuilder.AppendLine($"{key}: {value}"); 50 | } 51 | return stringBuilder.ToString(); 52 | } 53 | } 54 | 55 | // Extension method used to add the middleware to the HTTP request pipeline. 56 | public static class RequestResponseLoggingMiddlewareExtensions 57 | { 58 | public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder) 59 | { 60 | return builder.UseMiddleware(); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Project.UI/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Project.Infrastructure.Data; 3 | using Project.UI.Extensions; 4 | using Project.UI.Middlewares; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | builder.Services.AddDbContext(options => 9 | { 10 | options.UseSqlServer(builder.Configuration.GetConnectionString("PrimaryDbConnection")); 11 | }); 12 | 13 | builder.Services.AddControllersWithViews(); 14 | builder.Services.RegisterService(); 15 | 16 | // Add caching services 17 | builder.Services.AddMemoryCache(); 18 | 19 | // Register ILogger service 20 | builder.Services.AddLogging(); 21 | 22 | var app = builder.Build(); 23 | 24 | if (app.Environment.IsDevelopment()) 25 | { 26 | app.UseDeveloperExceptionPage(); 27 | } 28 | 29 | app.UseStaticFiles(); 30 | app.UseRouting(); 31 | 32 | #region Custom Middleware 33 | 34 | app.UseRequestResponseLogging(); 35 | #endregion 36 | 37 | 38 | 39 | //Please update your endpoint route here 40 | app.UseEndpoints(endpoints => 41 | { 42 | //endpoints.MapDefaultControllerRoute(); 43 | endpoints.MapControllerRoute(name: "default", pattern: "{controller=Product}/{action=Index}/{Id?}"); 44 | }); 45 | 46 | app.Run(); 47 | -------------------------------------------------------------------------------- /Project.UI/Project.UI.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 | -------------------------------------------------------------------------------- /Project.UI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:13470", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "http": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "applicationUrl": "http://localhost:5270", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "IIS Express": { 21 | "commandName": "IISExpress", 22 | "launchBrowser": true, 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Project.UI/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Index"; 3 | Layout = "~/Views/Shared/_Layout.cshtml"; 4 | } 5 | 6 |
7 |
8 | Welcome to Dashboard 9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /Project.UI/Views/Product/Create.cshtml: -------------------------------------------------------------------------------- 1 | @model ProductViewModel 2 | 3 | @{ 4 | ViewData["Title"] = "Create"; 5 | Layout = "~/Views/Shared/_Layout.cshtml"; 6 | } 7 | 8 |
9 | 10 |
11 | New Product 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 |
47 | 48 |
49 | Back 50 | 51 | 52 |
53 | 54 |
55 |
56 | 57 |
-------------------------------------------------------------------------------- /Project.UI/Views/Product/Details.cshtml: -------------------------------------------------------------------------------- 1 | @model ProductViewModel 2 | 3 | @{ 4 | ViewData["Title"] = "Details"; 5 | Layout = "~/Views/Shared/_Layout.cshtml"; 6 | } 7 | 8 |
9 | 10 |
11 | Product- @Model.Name 12 |
13 | 14 |
15 |
16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 | Back 42 |
43 |
44 | 45 |
-------------------------------------------------------------------------------- /Project.UI/Views/Product/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @model ProductViewModel 2 | 3 | @{ 4 | ViewData["Title"] = "Create"; 5 | Layout = "~/Views/Shared/_Layout.cshtml"; 6 | } 7 | 8 |
9 | 10 |
11 | Product Update 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 |
47 | 48 |
49 | Back 50 | 51 | 52 |
53 | 54 |
55 |
56 | 57 |
-------------------------------------------------------------------------------- /Project.UI/Views/Product/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IPagedList 2 | 3 | @{ 4 | ViewData["Title"] = "Products"; 5 | Layout = "~/Views/Shared/_Layout.cshtml"; 6 | } 7 |
8 |
9 | Products 10 | Create New 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | @foreach (var item in Model) 26 | { 27 | 28 | 29 | 30 | 31 | 32 | 42 | 43 | 48 | 49 | } 50 | 51 | 52 |
CodeNamePriceActiveAction
@item.Code@item.Name@item.Price 33 | @if (@item.IsActive) 34 | { 35 | Yes 36 | } 37 | else 38 | { 39 | No 40 | } 41 | 44 | Details 45 | Edit 46 | Delete 47 |
53 | 54 |
55 |
    56 | @if (Model.PageCount > 1) 57 | { 58 |
  • 59 | First 60 |
  • 61 |
  • 62 | Previous 63 |
  • 64 | @for (int i = 1; i <= Model.PageCount; i++) 65 | { 66 |
  • 67 | @i 68 |
  • 69 | } 70 |
  • 71 | Next 72 |
  • 73 |
  • 74 | Last 75 |
  • 76 | } 77 |
78 |
79 | 80 |
81 |
82 | 83 | -------------------------------------------------------------------------------- /Project.UI/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 40 |
41 | 42 | 43 |
44 | 53 |
54 | 55 | 56 | 57 | @**@ 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | @RenderSection("Scripts", false) 68 | 69 | 70 | -------------------------------------------------------------------------------- /Project.UI/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Project.Core.Entities.General; 2 | @using Project.UI; 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @using X.PagedList; 5 | @using X.PagedList.Mvc.Core 6 | @using X.PagedList.Web.Common; -------------------------------------------------------------------------------- /Project.UI/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Project.UI/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 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /Project.UI/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #F3F6FA; 3 | } 4 | 5 | p { 6 | font-family: 'Poppins', sans-serif; 7 | font-size: 1.1em; 8 | font-weight: 300; 9 | line-height: 1.7em; 10 | color: #999; 11 | } 12 | 13 | a, 14 | a:hover, 15 | a:focus { 16 | color: inherit; 17 | text-decoration: none; 18 | transition: all 0.3s; 19 | } 20 | 21 | .navbar { 22 | padding: 15px 10px; 23 | background: #fff; 24 | border: none; 25 | border-radius: 0; 26 | margin-bottom: 40px; 27 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1); 28 | } 29 | 30 | .navbar-btn { 31 | box-shadow: none; 32 | outline: none !important; 33 | border: none; 34 | } 35 | 36 | .line { 37 | width: 100%; 38 | height: 1px; 39 | border-bottom: 1px dashed #ddd; 40 | margin: 40px 0; 41 | } 42 | 43 | /* --------------------------------------------------- 44 | SIDEBAR STYLE 45 | ----------------------------------------------------- */ 46 | 47 | .wrapper { 48 | display: flex; 49 | width: 100%; 50 | } 51 | 52 | #sidebar { 53 | width: 250px; 54 | position: fixed; 55 | top: 0; 56 | left: 0; 57 | height: 100vh; 58 | z-index: 999; 59 | background: #00508B; 60 | color: #fff; 61 | transition: all 0.3s; 62 | } 63 | 64 | #sidebar.active { 65 | margin-left: -250px; 66 | } 67 | 68 | #sidebar .sidebar-header { 69 | padding: 11px 20px 10px 20px; 70 | background: white; 71 | } 72 | 73 | #sidebar ul.components { 74 | border-bottom: 1px solid #47748b; 75 | } 76 | 77 | #sidebar ul p { 78 | color: #fff; 79 | padding: 10px; 80 | } 81 | 82 | #sidebar ul li a { 83 | padding: 10px; 84 | font-size: 1.1em; 85 | display: block; 86 | } 87 | 88 | #sidebar ul li a:hover { 89 | color: white; 90 | background: #0092D1; 91 | } 92 | 93 | #sidebar ul li.active > a, 94 | a[aria-expanded="true"] { 95 | color: #fff; 96 | background: #0092D1; 97 | } 98 | 99 | a[data-toggle="collapse"] { 100 | position: relative; 101 | } 102 | 103 | .dropdown-toggle::after { 104 | display: block; 105 | position: absolute; 106 | top: 50%; 107 | right: 20px; 108 | transform: translateY(-50%); 109 | } 110 | 111 | ul ul a { 112 | font-size: 0.9em !important; 113 | padding-left: 30px !important; 114 | background: #00508B; 115 | } 116 | 117 | .menu-text { 118 | color: white; 119 | font-size: 24px; 120 | font-weight: bold; 121 | position: relative; 122 | top: 10px; 123 | left: 5px; 124 | } 125 | 126 | 127 | .menu-btn { 128 | background: #b0b4bf; 129 | margin-top: 10px; 130 | color: white; 131 | } 132 | 133 | .menu-header { 134 | width: 100%; 135 | height: 64px; 136 | background: #b0b4bf; 137 | } 138 | 139 | 140 | 141 | 142 | /* --------------------------------------------------- 143 | CONTENT STYLE 144 | ----------------------------------------------------- */ 145 | 146 | #content { 147 | width: calc(100% - 250px); 148 | min-height: 100vh; 149 | transition: all 0.3s; 150 | position: absolute; 151 | top: 0; 152 | right: 0; 153 | } 154 | 155 | #content.active { 156 | width: 100%; 157 | } 158 | 159 | /* --------------------------------------------------- 160 | MEDIAQUERIES 161 | ----------------------------------------------------- */ 162 | 163 | @media (max-width: 768px) { 164 | #sidebar { 165 | margin-left: -250px; 166 | } 167 | 168 | #sidebar.active { 169 | margin-left: 0; 170 | } 171 | 172 | #content { 173 | width: 100%; 174 | } 175 | 176 | #content.active { 177 | width: calc(100% - 250px); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Project.UI/wwwroot/images/nav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawser2133/clean-structured-project/e88d985f135030d979c55a23b8f60f05b003f9b9/Project.UI/wwwroot/images/nav.png -------------------------------------------------------------------------------- /Project.UI/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $("#sidebar").mCustomScrollbar({ 3 | theme: "minimal" 4 | }); 5 | $('#sidebarCollapse').on('click', function () { 6 | $('#sidebar, #content').toggleClass('active'); 7 | $('.collapse.in').toggleClass('in'); 8 | $('a[aria-expanded=true]').attr('aria-expanded', 'false'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /Project.UnitTest/Core/ProductServiceTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IMapper; 4 | using Project.Core.Interfaces.IRepositories; 5 | using Project.Core.Services; 6 | 7 | namespace Project.UnitTest 8 | { 9 | public class ProductServiceTests 10 | { 11 | private Mock> _productViewModelMapperMock; 12 | private Mock> _productMapperMock; 13 | private Mock _productRepositoryMock; 14 | 15 | [SetUp] 16 | public void Setup() 17 | { 18 | _productViewModelMapperMock = new Mock>(); 19 | _productMapperMock = new Mock>(); 20 | _productRepositoryMock = new Mock(); 21 | } 22 | 23 | [Test] 24 | public async Task CreateProductAsync_ValidProduct_ReturnsCreatedProductViewModel() 25 | { 26 | // Arrange 27 | var productService = new ProductService( 28 | _productViewModelMapperMock.Object, 29 | _productMapperMock.Object, 30 | _productRepositoryMock.Object); 31 | 32 | var newProductViewModel = new ProductViewModel 33 | { 34 | Code = "P001", 35 | Name = "Sample Product", 36 | Price = 9.99f, 37 | Description = "Sample description", 38 | IsActive = true 39 | }; 40 | 41 | var createdProduct = new Product 42 | { 43 | Code = "P001", 44 | Name = "Sample Product", 45 | Price = 9.99f, 46 | Description = "Sample description", 47 | IsActive = true 48 | }; 49 | 50 | _productMapperMock.Setup(mapper => mapper.MapModel(newProductViewModel)) 51 | .Returns(createdProduct); 52 | 53 | _productRepositoryMock.Setup(repo => repo.Create(createdProduct)) 54 | .ReturnsAsync(createdProduct); 55 | 56 | _productViewModelMapperMock.Setup(mapper => mapper.MapModel(createdProduct)) 57 | .Returns(newProductViewModel); 58 | 59 | // Act 60 | var result = await productService.Create(newProductViewModel); 61 | 62 | // Assert 63 | Assert.NotNull(result); 64 | Assert.That(result.Code, Is.EqualTo(newProductViewModel.Code)); 65 | // Additional assertions for other properties 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Project.UnitTest/UI/ProductControllerTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Logging; 4 | using Moq; 5 | using Project.Core.Entities.General; 6 | using Project.Core.Interfaces.IServices; 7 | using Project.UI.Controllers; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace Project.UnitTest.UI 15 | { 16 | public class ProductControllerTests 17 | { 18 | private Mock _productServiceMock; 19 | private Mock> _loggerMock; 20 | private ProductController _productController; 21 | 22 | [SetUp] 23 | public void Setup() 24 | { 25 | _productServiceMock = new Mock(); 26 | _loggerMock = new Mock>(); 27 | _productController = new ProductController(_loggerMock.Object, _productServiceMock.Object); 28 | } 29 | 30 | [Test] 31 | public async Task Index_ReturnsViewWithListOfProducts() 32 | { 33 | // Arrange 34 | var products = new List 35 | { 36 | new ProductViewModel { Id = 1, Code = "P001", Name = "Product A", Price = 9.99f, IsActive = true }, 37 | new ProductViewModel { Id = 2, Code = "P002", Name = "Product B", Price = 19.99f, IsActive = true } 38 | }; 39 | 40 | _productServiceMock.Setup(service => service.GetProducts()) 41 | .ReturnsAsync(products); 42 | 43 | // Act 44 | var result = await _productController.IndexWithoutPagination(); 45 | 46 | // Assert 47 | Assert.IsInstanceOf(result); 48 | var viewResult = (ViewResult)result; 49 | Assert.NotNull(viewResult); 50 | Assert.That(viewResult.Model, Is.InstanceOf>()); 51 | var model = (IEnumerable)viewResult.Model; 52 | Assert.NotNull(model); 53 | Assert.That(model.Count(), Is.EqualTo(products.Count)); 54 | } 55 | 56 | // Add more test methods for other controller actions, such as Create, Update, Delete, etc. 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Project.UnitTest/Usings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Structured Project - ASP.NET Core 2 | 3 | This template is for a clean structured ASP.NET Core web project, follows the Clean Architecture principles, SOLID design principles, implements the Dependency Injection, Repository, and Unit of Work design pattern, and utilizes Entity Framework Core for data access. It provides a standardized structure and organization for building robust and maintainable ASP.NET Core web applications with complete CRUD (Create, Read, Update, Delete) operations. Feel free to ask any questions or share your thoughts. 4 | 5 | #### Reference 6 | You can visit my blog post- [Clean Structured Project – ASP.NET Core](https://binarybytez.com/clean-structured-project/) 7 | 8 | ## Project Structure 9 | 10 | The [project structure](https://binarybytez.com/understanding-clean-architecture/) 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, domain models, view models, etc. 15 | │ ├── Infrastructure # Contains infrastructure concerns such as data access, external services, etc. 16 | │ └── UI # Contains the user interface layer, including controllers, views, 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 | │ └── UI.Tests # Contains unit tests for the UI layer 21 | └── README.md # Project documentation (you are here!) 22 | ``` 23 | 24 | ## Getting Started 25 | 26 | To use this project template, follow the steps below: 27 | 28 | 1. Ensure the .NET 7 SDK is installed on your machine. 29 | 2. Clone or download this repository to your local machine. 30 | 3. Open the solution in your preferred IDE (e.g., Visual Studio, Visual Studio Code). 31 | 4. Build the solution to restore NuGet packages and compile the code. 32 | 5. Configure the necessary database connection settings in the `appsettings.json` file of the Infrastructure project. 33 | 6. Open the Package Manager Console, select `Project.Infrastructure` project, and run the `Update-Database` command to create the database 34 | 7. Run the application by starting the `Project.UI` project. 35 | 36 | ## Project Features 37 | 38 | This project template includes the following features: 39 | 40 | - **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. 41 | - **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. 42 | - **Repository Pattern**: The [repository pattern](https://binarybytez.com/understanding-clean-architecture/) abstracts the data access layer and provides a consistent interface for working with data. 43 | - **Unit of Work Pattern**: The unit of work pattern helps manage transactions and ensures consistency when working with multiple repositories. 44 | - **Entity Framework Core**: The project utilizes Entity Framework Core as the ORM (Object-Relational Mapping) tool for data access. 45 | - **ASP.NET Core Web**: The project includes an [ASP.NET Core web project](https://binarybytez.com/clean-structured-project/) that serves as the user interface layer, handling HTTP requests and responses. 46 | - **CRUD Operations**: The project template provides a foundation for implementing complete CRUD (Create, Read, Update, Delete) operations on entities using Entity Framework Core. 47 | - **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. 48 | - **Unit Testing**: The solution includes separate test projects for unit testing the core, infrastructure, and UI layers. 49 | 50 | ## Usage 51 | 52 | [The project template](https://binarybytez.com/clean-structured-project/) provides a starting point for implementing CRUD operations on entities using Entity Framework 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 the CRUD operations: 53 | 54 | 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. 55 | 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. 56 | 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. 57 | 4. **Controllers**: The `UI` project contains controllers that handle HTTP requests and responses. Update or create new controllers to expose the CRUD endpoints for your entities. 58 | 59 | 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/). 60 | 61 | ## Authors 62 | 63 | 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) 64 | 65 | Buy Me A Coffee
66 | **Thanks for your support!** 67 | 68 | ## Contributing 69 | 70 | 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. 71 | 72 | ## License 73 | 74 | This project is licensed under the [MIT License](LICENSE). 75 | --------------------------------------------------------------------------------