├── .gitignore ├── LICENSE ├── Project.API ├── Controllers │ └── V1 │ │ ├── AuthController.cs │ │ ├── ProductController.cs │ │ ├── RoleController.cs │ │ └── UserController.cs ├── Extensions │ ├── ConfigureSwaggerOptions.cs │ ├── MapperExtension.cs │ ├── SecurityExtension.cs │ ├── ServiceExtension.cs │ └── SwaggerDefaultValues.cs ├── Helpers │ └── ModelStateHelper.cs ├── Middlewares │ └── RequestResponseLoggingMiddleware.cs ├── Program.cs ├── Project.API.csproj ├── Properties │ └── launchSettings.json ├── appsettings.Development.json └── appsettings.json ├── Project.Core ├── Common │ ├── AppSettings.cs │ ├── ExpressionBuilder.cs │ ├── ExpressionFilter.cs │ └── JwtConfig.cs ├── Entities │ ├── Business │ │ ├── AuthResultViewModel.cs │ │ ├── ErrorViewModel.cs │ │ ├── LoginViewModel.cs │ │ ├── PaginatedDataViewModel.cs │ │ ├── ProductViewModel.cs │ │ ├── ResetPasswordViewModel.cs │ │ ├── ResponseViewModel.cs │ │ ├── RoleViewModel.cs │ │ └── UserViewModel.cs │ └── General │ │ ├── Base.cs │ │ ├── Product.cs │ │ ├── Role.cs │ │ └── User.cs ├── Exceptions │ └── NotFoundException.cs ├── Interfaces │ ├── IMapper │ │ └── IBaseMapper.cs │ ├── IRepositories │ │ ├── IAuthRepository.cs │ │ ├── IBaseRepository.cs │ │ ├── IProductRepository.cs │ │ ├── IRoleRepository.cs │ │ └── IUserRepository.cs │ └── IServices │ │ ├── IAuthService.cs │ │ ├── IBaseService.cs │ │ ├── IProductService.cs │ │ ├── IRoleService.cs │ │ ├── IUserContext.cs │ │ └── IUserService.cs ├── Mapper │ └── BaseMapper.cs ├── Project.Core.csproj └── Services │ ├── AuthService.cs │ ├── BaseService.cs │ ├── ProductService.cs │ ├── RoleService.cs │ ├── UserContext.cs │ └── UserService.cs ├── Project.Infrastructure ├── Data │ ├── ApplicationDbContext.cs │ ├── ApplicationDbContextConfigurations.cs │ └── ApplicationDbContextSeed.cs ├── Migrations │ ├── 20240127163649_Initial.Designer.cs │ ├── 20240127163649_Initial.cs │ └── ApplicationDbContextModelSnapshot.cs ├── Project.Infrastructure.csproj └── Repositories │ ├── AuthRepository.cs │ ├── BaseRepository.cs │ ├── ProductRepository.cs │ ├── RoleRepository.cs │ └── UserRepository.cs ├── Project.UnitTest ├── API │ └── ProductControllerTests.cs ├── Core │ └── ProductServiceTests.cs ├── Infrastructure │ └── ProductRepositoryTests.cs ├── Project.UnitTest.csproj └── Usings.cs ├── README.md └── WebApiProject.sln /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Kawser Hamid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Project.API/Controllers/V1/AuthController.cs: -------------------------------------------------------------------------------- 1 | using Asp.Versioning; 2 | using Project.API.Helpers; 3 | using Project.Core.Entities.Business; 4 | using Project.Core.Interfaces.IServices; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.IdentityModel.Tokens; 7 | using System.IdentityModel.Tokens.Jwt; 8 | using System.Security.Claims; 9 | using System.Text; 10 | using Project.Core.Common; 11 | using Microsoft.Extensions.Options; 12 | 13 | namespace Project.API.Controllers.V1 14 | { 15 | [ApiVersion("1.0")] 16 | [Route("api/v{version:apiVersion}/[controller]")] 17 | [ApiController] 18 | public class AuthController : ControllerBase 19 | { 20 | private readonly ILogger _logger; 21 | private readonly IAuthService _authService; 22 | private readonly IConfiguration _configuration; 23 | private readonly AppSettings _appSettings; 24 | 25 | public AuthController( 26 | ILogger logger, 27 | IAuthService authService, 28 | IConfiguration configuration, 29 | IOptions appSettings) 30 | { 31 | _logger = logger; 32 | _authService = authService; 33 | _configuration = configuration; 34 | _appSettings = appSettings.Value; 35 | } 36 | 37 | [HttpPost, Route("login")] 38 | public async Task Login(LoginViewModel model) 39 | { 40 | if (ModelState.IsValid) 41 | { 42 | try 43 | { 44 | var result = await _authService.Login(model.UserName, model.Password); 45 | if (result.Success) 46 | { 47 | var token = GenerateJwtToken(result); 48 | return Ok(new ResponseViewModel 49 | { 50 | Success = true, 51 | Data = token, 52 | Message = "Login successful" 53 | }); 54 | } 55 | 56 | return BadRequest(result); 57 | } 58 | catch (Exception ex) 59 | { 60 | _logger.LogError(ex, $"An error occurred while login"); 61 | string message = $"An error occurred while login- " + ex.Message; 62 | 63 | return StatusCode(StatusCodes.Status500InternalServerError, new ResponseViewModel 64 | { 65 | Success = false, 66 | Message = message, 67 | Error = new ErrorViewModel 68 | { 69 | Code = "LOGIN_ERROR", 70 | Message = message 71 | } 72 | }); 73 | } 74 | 75 | } 76 | 77 | return BadRequest(new ResponseViewModel 78 | { 79 | Success = false, 80 | Message = "Invalid input", 81 | Error = new ErrorViewModel 82 | { 83 | Code = "INPUT_VALIDATION_ERROR", 84 | Message = ModelStateHelper.GetErrors(ModelState) 85 | } 86 | }); 87 | } 88 | 89 | [HttpPost, Route("logout")] 90 | public async Task Logout() 91 | { 92 | await _authService.Logout(); 93 | return Ok(); 94 | } 95 | 96 | private AuthResultViewModel GenerateJwtToken(ResponseViewModel auth) 97 | { 98 | var jwtTokenHandler = new JwtSecurityTokenHandler(); 99 | var key = Encoding.ASCII.GetBytes(_appSettings.JwtConfig.Secret); 100 | 101 | var claims = new List 102 | { 103 | new Claim(JwtRegisteredClaimNames.Aud, _appSettings.JwtConfig.ValidAudience), 104 | new Claim(JwtRegisteredClaimNames.Iss, _appSettings.JwtConfig.ValidIssuer), 105 | new Claim(JwtRegisteredClaimNames.Sub, auth.Data.Id.ToString()), 106 | new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) 107 | }; 108 | 109 | var tokenDescriptor = new SecurityTokenDescriptor 110 | { 111 | Subject = new ClaimsIdentity(claims), 112 | Expires = DateTime.Now.AddMinutes(Convert.ToDouble(_appSettings.JwtConfig.TokenExpirationMinutes)), 113 | SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) 114 | }; 115 | 116 | var token = jwtTokenHandler.CreateToken(tokenDescriptor); 117 | var jwtToken = jwtTokenHandler.WriteToken(token); 118 | 119 | return new AuthResultViewModel() 120 | { 121 | AccessToken = jwtToken, 122 | Success = true, 123 | }; 124 | } 125 | 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /Project.API/Controllers/V1/ProductController.cs: -------------------------------------------------------------------------------- 1 | using Asp.Versioning; 2 | using Microsoft.AspNetCore.Authentication.JwtBearer; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using Project.API.Helpers; 7 | using Project.Core.Common; 8 | using Project.Core.Entities.Business; 9 | using Project.Core.Entities.General; 10 | using Project.Core.Interfaces.IServices; 11 | 12 | namespace Project.API.Controllers.V1 13 | { 14 | [ApiVersion("1.0")] 15 | [Route("api/v{version:apiVersion}/[controller]")] 16 | [ApiController] 17 | [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 18 | public class ProductController : ControllerBase 19 | { 20 | private readonly ILogger _logger; 21 | private readonly IProductService _productService; 22 | private readonly IMemoryCache _memoryCache; 23 | 24 | public ProductController(ILogger logger, IProductService productService, IMemoryCache memoryCache) 25 | { 26 | _logger = logger; 27 | _productService = productService; 28 | _memoryCache = memoryCache; 29 | } 30 | 31 | [HttpGet("paginated-data")] 32 | [AllowAnonymous] 33 | public async Task Get(int? pageNumber, int? pageSize, string? search, string? sortBy, string? sortOrder, CancellationToken cancellationToken) 34 | { 35 | try 36 | { 37 | int pageSizeValue = pageSize ?? 10; 38 | int pageNumberValue = pageNumber ?? 1; 39 | sortBy = sortBy ?? "Id"; 40 | sortOrder = sortOrder ?? "desc"; 41 | 42 | var filters = new List(); 43 | if (!string.IsNullOrWhiteSpace(search) && search != null) 44 | { 45 | // Add filters for relevant properties 46 | filters.AddRange(new[] 47 | { 48 | new ExpressionFilter 49 | { 50 | PropertyName = "Code", 51 | Value = search, 52 | Comparison = Comparison.Contains 53 | }, 54 | new ExpressionFilter 55 | { 56 | PropertyName = "Name", 57 | Value = search, 58 | Comparison = Comparison.Contains 59 | }, 60 | new ExpressionFilter 61 | { 62 | PropertyName = "Description", 63 | Value = search, 64 | Comparison = Comparison.Contains 65 | } 66 | }); 67 | 68 | // Check if the search string represents a valid numeric value for the "Price" property 69 | if (double.TryParse(search, out double price)) 70 | { 71 | filters.Add(new ExpressionFilter 72 | { 73 | PropertyName = "Price", 74 | Value = price, 75 | Comparison = Comparison.Equal 76 | }); 77 | } 78 | } 79 | 80 | var products = await _productService.GetPaginatedData(pageNumberValue, pageSizeValue, filters, sortBy, sortOrder, cancellationToken); 81 | 82 | var response = new ResponseViewModel> 83 | { 84 | Success = true, 85 | Message = "Products retrieved successfully", 86 | Data = products 87 | }; 88 | 89 | return Ok(response); 90 | } 91 | catch (Exception ex) 92 | { 93 | _logger.LogError(ex, "An error occurred while retrieving products"); 94 | 95 | var errorResponse = new ResponseViewModel> 96 | { 97 | Success = false, 98 | Message = "Error retrieving products", 99 | Error = new ErrorViewModel 100 | { 101 | Code = "ERROR_CODE", 102 | Message = ex.Message 103 | } 104 | }; 105 | 106 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 107 | } 108 | } 109 | 110 | [HttpGet] 111 | [AllowAnonymous] 112 | public async Task Get(CancellationToken cancellationToken) 113 | { 114 | try 115 | { 116 | var products = await _productService.GetAll(cancellationToken); 117 | 118 | var response = new ResponseViewModel> 119 | { 120 | Success = true, 121 | Message = "Products retrieved successfully", 122 | Data = products 123 | }; 124 | 125 | return Ok(response); 126 | } 127 | catch (Exception ex) 128 | { 129 | _logger.LogError(ex, "An error occurred while retrieving products"); 130 | 131 | var errorResponse = new ResponseViewModel> 132 | { 133 | Success = false, 134 | Message = "Error retrieving products", 135 | Error = new ErrorViewModel 136 | { 137 | Code = "ERROR_CODE", 138 | Message = ex.Message 139 | } 140 | }; 141 | 142 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 143 | } 144 | } 145 | 146 | [HttpGet("{id}")] 147 | [AllowAnonymous] 148 | public async Task Get(int id, CancellationToken cancellationToken) 149 | { 150 | try 151 | { 152 | var product = new ProductViewModel(); 153 | 154 | // Attempt to retrieve the product from the cache 155 | if (_memoryCache.TryGetValue($"Product_{id}", out ProductViewModel cachedProduct)) 156 | { 157 | product = cachedProduct; 158 | } 159 | else 160 | { 161 | // If not found in cache, fetch the product from the data source 162 | product = await _productService.GetById(id, cancellationToken); 163 | 164 | if (product != null) 165 | { 166 | // Cache the product with an expiration time of 10 minutes 167 | _memoryCache.Set($"Product_{id}", product, TimeSpan.FromMinutes(10)); 168 | } 169 | } 170 | 171 | var response = new ResponseViewModel 172 | { 173 | Success = true, 174 | Message = "Product retrieved successfully", 175 | Data = product 176 | }; 177 | 178 | return Ok(response); 179 | } 180 | catch (Exception ex) 181 | { 182 | if (ex.Message == "No data found") 183 | { 184 | return StatusCode(StatusCodes.Status404NotFound, new ResponseViewModel 185 | { 186 | Success = false, 187 | Message = "Product not found", 188 | Error = new ErrorViewModel 189 | { 190 | Code = "NOT_FOUND", 191 | Message = "Product not found" 192 | } 193 | }); 194 | } 195 | 196 | _logger.LogError(ex, $"An error occurred while retrieving the product"); 197 | 198 | var errorResponse = new ResponseViewModel 199 | { 200 | Success = false, 201 | Message = "Error retrieving product", 202 | Error = new ErrorViewModel 203 | { 204 | Code = "ERROR_CODE", 205 | Message = ex.Message 206 | } 207 | }; 208 | 209 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 210 | } 211 | } 212 | 213 | [HttpPost] 214 | public async Task Create(ProductCreateViewModel model, CancellationToken cancellationToken) 215 | { 216 | if (ModelState.IsValid) 217 | { 218 | string message = ""; 219 | if (await _productService.IsExists("Name", model.Name, cancellationToken)) 220 | { 221 | message = $"The product name- '{model.Name}' already exists"; 222 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 223 | { 224 | Success = false, 225 | Message = message, 226 | Error = new ErrorViewModel 227 | { 228 | Code = "DUPLICATE_NAME", 229 | Message = message 230 | } 231 | }); 232 | } 233 | 234 | if (await _productService.IsExists("Code", model.Code, cancellationToken)) 235 | { 236 | message = $"The product code- '{model.Code}' already exists"; 237 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 238 | { 239 | Success = false, 240 | Message = message, 241 | Error = new ErrorViewModel 242 | { 243 | Code = "DUPLICATE_CODE", 244 | Message = message 245 | } 246 | }); 247 | } 248 | 249 | try 250 | { 251 | var data = await _productService.Create(model, cancellationToken); 252 | 253 | var response = new ResponseViewModel 254 | { 255 | Success = true, 256 | Message = "Product created successfully", 257 | Data = data 258 | }; 259 | 260 | return Ok(response); 261 | } 262 | catch (Exception ex) 263 | { 264 | _logger.LogError(ex, $"An error occurred while adding the product"); 265 | message = $"An error occurred while adding the product- " + ex.Message; 266 | 267 | return StatusCode(StatusCodes.Status500InternalServerError, new ResponseViewModel 268 | { 269 | Success = false, 270 | Message = message, 271 | Error = new ErrorViewModel 272 | { 273 | Code = "ADD_ROLE_ERROR", 274 | Message = message 275 | } 276 | }); 277 | } 278 | } 279 | 280 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 281 | { 282 | Success = false, 283 | Message = "Invalid input", 284 | Error = new ErrorViewModel 285 | { 286 | Code = "INPUT_VALIDATION_ERROR", 287 | Message = ModelStateHelper.GetErrors(ModelState) 288 | } 289 | }); 290 | } 291 | 292 | [HttpPut] 293 | [AllowAnonymous] 294 | public async Task Edit(ProductUpdateViewModel model, CancellationToken cancellationToken) 295 | { 296 | if (ModelState.IsValid) 297 | { 298 | string message = ""; 299 | if (await _productService.IsExistsForUpdate(model.Id, "Name", model.Name, cancellationToken)) 300 | { 301 | message = $"The product name- '{model.Name}' already exists"; 302 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 303 | { 304 | Success = false, 305 | Message = message, 306 | Error = new ErrorViewModel 307 | { 308 | Code = "DUPLICATE_NAME", 309 | Message = message 310 | } 311 | }); 312 | } 313 | 314 | if (await _productService.IsExistsForUpdate(model.Id, "Code", model.Code, cancellationToken)) 315 | { 316 | message = $"The product code- '{model.Code}' already exists"; 317 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 318 | { 319 | Success = false, 320 | Message = message, 321 | Error = new ErrorViewModel 322 | { 323 | Code = "DUPLICATE_CODE", 324 | Message = message 325 | } 326 | }); 327 | } 328 | 329 | try 330 | { 331 | await _productService.Update(model, cancellationToken); 332 | 333 | // Remove data from cache by key 334 | _memoryCache.Remove($"Product_{model.Id}"); 335 | 336 | var response = new ResponseViewModel 337 | { 338 | Success = true, 339 | Message = "Product updated successfully" 340 | }; 341 | 342 | return Ok(response); 343 | } 344 | catch (Exception ex) 345 | { 346 | _logger.LogError(ex, $"An error occurred while updating the product"); 347 | message = $"An error occurred while updating the product- " + ex.Message; 348 | 349 | return StatusCode(StatusCodes.Status500InternalServerError, new ResponseViewModel 350 | { 351 | Success = false, 352 | Message = message, 353 | Error = new ErrorViewModel 354 | { 355 | Code = "UPDATE_ROLE_ERROR", 356 | Message = message 357 | } 358 | }); 359 | } 360 | } 361 | 362 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 363 | { 364 | Success = false, 365 | Message = "Invalid input", 366 | Error = new ErrorViewModel 367 | { 368 | Code = "INPUT_VALIDATION_ERROR", 369 | Message = ModelStateHelper.GetErrors(ModelState) 370 | } 371 | }); 372 | } 373 | 374 | [HttpDelete("{id}")] 375 | public async Task Delete(int id, CancellationToken cancellationToken) 376 | { 377 | try 378 | { 379 | await _productService.Delete(id, cancellationToken); 380 | 381 | // Remove data from cache by key 382 | _memoryCache.Remove($"Product_{id}"); 383 | 384 | var response = new ResponseViewModel 385 | { 386 | Success = true, 387 | Message = "Product deleted successfully" 388 | }; 389 | 390 | return Ok(response); 391 | } 392 | catch (Exception ex) 393 | { 394 | if (ex.Message == "No data found") 395 | { 396 | return StatusCode(StatusCodes.Status404NotFound, new ResponseViewModel 397 | { 398 | Success = false, 399 | Message = "Product not found", 400 | Error = new ErrorViewModel 401 | { 402 | Code = "NOT_FOUND", 403 | Message = "Product not found" 404 | } 405 | }); 406 | } 407 | 408 | _logger.LogError(ex, "An error occurred while deleting the product"); 409 | 410 | return StatusCode(StatusCodes.Status500InternalServerError, new ResponseViewModel 411 | { 412 | Success = false, 413 | Message = "Error deleting the product", 414 | Error = new ErrorViewModel 415 | { 416 | Code = "DELETE_ROLE_ERROR", 417 | Message = ex.Message 418 | } 419 | }); 420 | 421 | } 422 | } 423 | } 424 | 425 | } 426 | -------------------------------------------------------------------------------- /Project.API/Controllers/V1/RoleController.cs: -------------------------------------------------------------------------------- 1 | using Asp.Versioning; 2 | using Project.API.Helpers; 3 | using Project.Core.Entities.Business; 4 | using Project.Core.Interfaces.IServices; 5 | using Microsoft.AspNetCore.Authentication.JwtBearer; 6 | using Microsoft.AspNetCore.Authorization; 7 | using Microsoft.AspNetCore.Mvc; 8 | using System.Security.Claims; 9 | 10 | namespace Project.API.Controllers.V1 11 | { 12 | [ApiVersion("1.0")] 13 | [Route("api/v{version:apiVersion}/[controller]")] 14 | [ApiController] 15 | [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 16 | public class RoleController : ControllerBase 17 | { 18 | private readonly ILogger _logger; 19 | private readonly IRoleService _roleService; 20 | 21 | public RoleController(ILogger logger, IRoleService roleService) 22 | { 23 | _logger = logger; 24 | _roleService = roleService; 25 | } 26 | 27 | [HttpGet("paginated")] 28 | public async Task Get(int? pageNumber, int? pageSize, CancellationToken cancellationToken) 29 | { 30 | try 31 | { 32 | int pageSizeValue = pageSize ?? 10; 33 | int pageNumberValue = pageNumber ?? 1; 34 | 35 | var roles = await _roleService.GetPaginatedData(pageNumberValue, pageSizeValue, cancellationToken); 36 | 37 | var response = new ResponseViewModel> 38 | { 39 | Success = true, 40 | Message = "Roles retrieved successfully", 41 | Data = roles 42 | }; 43 | 44 | return Ok(response); 45 | } 46 | catch (Exception ex) 47 | { 48 | _logger.LogError(ex, "An error occurred while retrieving roles"); 49 | 50 | var errorResponse = new ResponseViewModel> 51 | { 52 | Success = false, 53 | Message = "Error retrieving roles", 54 | Error = new ErrorViewModel 55 | { 56 | Code = "ERROR_CODE", 57 | Message = ex.Message 58 | } 59 | }; 60 | 61 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 62 | } 63 | } 64 | 65 | [HttpGet] 66 | public async Task Get(CancellationToken cancellationToken) 67 | { 68 | try 69 | { 70 | var roles = await _roleService.GetAll(cancellationToken); 71 | 72 | var response = new ResponseViewModel> 73 | { 74 | Success = true, 75 | Message = "Roles retrieved successfully", 76 | Data = roles 77 | }; 78 | 79 | return Ok(response); 80 | } 81 | catch (Exception ex) 82 | { 83 | _logger.LogError(ex, "An error occurred while retrieving roles"); 84 | 85 | var errorResponse = new ResponseViewModel> 86 | { 87 | Success = false, 88 | Message = "Error retrieving roles", 89 | Error = new ErrorViewModel 90 | { 91 | Code = "ERROR_CODE", 92 | Message = ex.Message 93 | } 94 | }; 95 | 96 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 97 | } 98 | } 99 | 100 | [HttpGet("{id}")] 101 | public async Task Get(int id, CancellationToken cancellationToken) 102 | { 103 | try 104 | { 105 | var data = await _roleService.GetById(id, cancellationToken); 106 | 107 | var response = new ResponseViewModel 108 | { 109 | Success = true, 110 | Message = "Role retrieved successfully", 111 | Data = data 112 | }; 113 | 114 | return Ok(response); 115 | } 116 | catch (Exception ex) 117 | { 118 | if (ex.Message == "No data found") 119 | { 120 | return StatusCode(StatusCodes.Status404NotFound, new ResponseViewModel 121 | { 122 | Success = false, 123 | Message = "Role not found", 124 | Error = new ErrorViewModel 125 | { 126 | Code = "NOT_FOUND", 127 | Message = "Role not found" 128 | } 129 | }); 130 | } 131 | 132 | _logger.LogError(ex, $"An error occurred while retrieving the role"); 133 | 134 | var errorResponse = new ResponseViewModel 135 | { 136 | Success = false, 137 | Message = "Error retrieving role", 138 | Error = new ErrorViewModel 139 | { 140 | Code = "ERROR_CODE", 141 | Message = ex.Message 142 | } 143 | }; 144 | 145 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 146 | } 147 | } 148 | 149 | [HttpPost] 150 | public async Task Create(RoleCreateViewModel model, CancellationToken cancellationToken) 151 | { 152 | if (ModelState.IsValid) 153 | { 154 | string message = ""; 155 | if (await _roleService.IsExists("Name", model.Name, cancellationToken)) 156 | { 157 | message = $"The role name- '{model.Name}' already exists"; 158 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 159 | { 160 | Success = false, 161 | Message = message, 162 | Error = new ErrorViewModel 163 | { 164 | Code = "DUPLICATE_NAME", 165 | Message = message 166 | } 167 | }); 168 | } 169 | 170 | if (await _roleService.IsExists("Code", model.Code, cancellationToken)) 171 | { 172 | message = $"The role code- '{model.Code}' already exists"; 173 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 174 | { 175 | Success = false, 176 | Message = message, 177 | Error = new ErrorViewModel 178 | { 179 | Code = "DUPLICATE_CODE", 180 | Message = message 181 | } 182 | }); 183 | } 184 | 185 | try 186 | { 187 | var data = await _roleService.Create(model, cancellationToken); 188 | 189 | var response = new ResponseViewModel 190 | { 191 | Success = true, 192 | Message = "Role created successfully", 193 | Data = data 194 | }; 195 | 196 | return Ok(response); 197 | } 198 | catch (Exception ex) 199 | { 200 | _logger.LogError(ex, $"An error occurred while adding the role"); 201 | message = $"An error occurred while adding the role- " + ex.Message; 202 | 203 | return StatusCode(StatusCodes.Status500InternalServerError, new ResponseViewModel 204 | { 205 | Success = false, 206 | Message = message, 207 | Error = new ErrorViewModel 208 | { 209 | Code = "ADD_ROLE_ERROR", 210 | Message = message 211 | } 212 | }); 213 | } 214 | } 215 | 216 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 217 | { 218 | Success = false, 219 | Message = "Invalid input", 220 | Error = new ErrorViewModel 221 | { 222 | Code = "INPUT_VALIDATION_ERROR", 223 | Message = ModelStateHelper.GetErrors(ModelState) 224 | } 225 | }); 226 | } 227 | 228 | [HttpPut] 229 | public async Task Edit(RoleUpdateViewModel model, CancellationToken cancellationToken) 230 | { 231 | if (ModelState.IsValid) 232 | { 233 | string message = ""; 234 | if (await _roleService.IsExistsForUpdate(model.Id, "Name", model.Name, cancellationToken)) 235 | { 236 | message = $"The role name- '{model.Name}' already exists"; 237 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 238 | { 239 | Success = false, 240 | Message = message, 241 | Error = new ErrorViewModel 242 | { 243 | Code = "DUPLICATE_NAME", 244 | Message = message 245 | } 246 | }); 247 | } 248 | 249 | if (await _roleService.IsExistsForUpdate(model.Id, "Code", model.Code, cancellationToken)) 250 | { 251 | message = $"The role code- '{model.Code}' already exists"; 252 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 253 | { 254 | Success = false, 255 | Message = message, 256 | Error = new ErrorViewModel 257 | { 258 | Code = "DUPLICATE_CODE", 259 | Message = message 260 | } 261 | }); 262 | } 263 | 264 | try 265 | { 266 | await _roleService.Update(model, cancellationToken); 267 | 268 | var response = new ResponseViewModel 269 | { 270 | Success = true, 271 | Message = "Role updated successfully" 272 | }; 273 | 274 | return Ok(response); 275 | } 276 | catch (Exception ex) 277 | { 278 | _logger.LogError(ex, $"An error occurred while updating the role"); 279 | message = $"An error occurred while updating the role- " + ex.Message; 280 | 281 | return StatusCode(StatusCodes.Status500InternalServerError, new ResponseViewModel 282 | { 283 | Success = false, 284 | Message = message, 285 | Error = new ErrorViewModel 286 | { 287 | Code = "UPDATE_ROLE_ERROR", 288 | Message = message 289 | } 290 | }); 291 | } 292 | } 293 | 294 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 295 | { 296 | Success = false, 297 | Message = "Invalid input", 298 | Error = new ErrorViewModel 299 | { 300 | Code = "INPUT_VALIDATION_ERROR", 301 | Message = ModelStateHelper.GetErrors(ModelState) 302 | } 303 | }); 304 | } 305 | 306 | [HttpDelete("{id}")] 307 | public async Task Delete(int id, CancellationToken cancellationToken) 308 | { 309 | try 310 | { 311 | await _roleService.Delete(id, cancellationToken); 312 | 313 | var response = new ResponseViewModel 314 | { 315 | Success = true, 316 | Message = "Role deleted successfully" 317 | }; 318 | 319 | return Ok(response); 320 | } 321 | catch (Exception ex) 322 | { 323 | if (ex.Message == "No data found") 324 | { 325 | return StatusCode(StatusCodes.Status404NotFound, new ResponseViewModel 326 | { 327 | Success = false, 328 | Message = "Role not found", 329 | Error = new ErrorViewModel 330 | { 331 | Code = "NOT_FOUND", 332 | Message = "Role not found" 333 | } 334 | }); 335 | } 336 | 337 | _logger.LogError(ex, "An error occurred while deleting the role"); 338 | 339 | return StatusCode(StatusCodes.Status500InternalServerError, new ResponseViewModel 340 | { 341 | Success = false, 342 | Message = "Error deleting the role", 343 | Error = new ErrorViewModel 344 | { 345 | Code = "DELETE_ROLE_ERROR", 346 | Message = ex.Message 347 | } 348 | }); 349 | 350 | } 351 | } 352 | } 353 | 354 | } 355 | -------------------------------------------------------------------------------- /Project.API/Controllers/V1/UserController.cs: -------------------------------------------------------------------------------- 1 | using Asp.Versioning; 2 | using Project.API.Helpers; 3 | using Project.Core.Entities.Business; 4 | using Project.Core.Interfaces.IServices; 5 | using Microsoft.AspNetCore.Authentication.JwtBearer; 6 | using Microsoft.AspNetCore.Authorization; 7 | using Microsoft.AspNetCore.Mvc; 8 | 9 | namespace Project.API.Controllers.V1 10 | { 11 | [ApiVersion("1.0")] 12 | [Route("api/v{version:apiVersion}/[controller]")] 13 | [ApiController] 14 | [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 15 | public class UserController : ControllerBase 16 | { 17 | private readonly ILogger _logger; 18 | private readonly IUserService _userService; 19 | 20 | public UserController(ILogger logger, IUserService userService) 21 | { 22 | _logger = logger; 23 | _userService = userService; 24 | } 25 | 26 | [HttpGet("paginated")] 27 | public async Task Get(int? pageNumber, int? pageSize, CancellationToken cancellationToken) 28 | { 29 | try 30 | { 31 | int pageSizeValue = pageSize ?? 10; 32 | int pageNumberValue = pageNumber ?? 1; 33 | 34 | var users = await _userService.GetPaginatedData(pageNumberValue, pageSizeValue, cancellationToken); 35 | 36 | var response = new ResponseViewModel> 37 | { 38 | Success = true, 39 | Message = "Users retrieved successfully", 40 | Data = users 41 | }; 42 | 43 | return Ok(response); 44 | } 45 | catch (Exception ex) 46 | { 47 | _logger.LogError(ex, "An error occurred while retrieving users"); 48 | 49 | var errorResponse = new ResponseViewModel> 50 | { 51 | Success = false, 52 | Message = "Error retrieving users", 53 | Error = new ErrorViewModel 54 | { 55 | Code = "ERROR_CODE", 56 | Message = ex.Message 57 | } 58 | }; 59 | 60 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 61 | } 62 | } 63 | 64 | [HttpGet] 65 | public async Task Get(CancellationToken cancellationToken) 66 | { 67 | try 68 | { 69 | var users = await _userService.GetAll(cancellationToken); 70 | 71 | var response = new ResponseViewModel> 72 | { 73 | Success = true, 74 | Message = "Users retrieved successfully", 75 | Data = users 76 | }; 77 | 78 | return Ok(response); 79 | } 80 | catch (Exception ex) 81 | { 82 | _logger.LogError(ex, "An error occurred while retrieving users"); 83 | 84 | var errorResponse = new ResponseViewModel> 85 | { 86 | Success = false, 87 | Message = "Error retrieving users", 88 | Error = new ErrorViewModel 89 | { 90 | Code = "ERROR_CODE", 91 | Message = ex.Message 92 | } 93 | }; 94 | 95 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 96 | } 97 | } 98 | 99 | [HttpGet("{id}")] 100 | public async Task Get(int id, CancellationToken cancellationToken) 101 | { 102 | try 103 | { 104 | var data = await _userService.GetById(id, cancellationToken); 105 | 106 | var response = new ResponseViewModel 107 | { 108 | Success = true, 109 | Message = "User retrieved successfully", 110 | Data = data 111 | }; 112 | 113 | return Ok(response); 114 | } 115 | catch (Exception ex) 116 | { 117 | if (ex.Message == "No data found") 118 | { 119 | return StatusCode(StatusCodes.Status404NotFound, new ResponseViewModel 120 | { 121 | Success = false, 122 | Message = "User not found", 123 | Error = new ErrorViewModel 124 | { 125 | Code = "NOT_FOUND", 126 | Message = "User not found" 127 | } 128 | }); 129 | } 130 | 131 | _logger.LogError(ex, $"An error occurred while retrieving the user"); 132 | 133 | var errorResponse = new ResponseViewModel 134 | { 135 | Success = false, 136 | Message = "Error retrieving user", 137 | Error = new ErrorViewModel 138 | { 139 | Code = "ERROR_CODE", 140 | Message = ex.Message 141 | } 142 | }; 143 | 144 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 145 | } 146 | } 147 | 148 | [HttpPost] 149 | public async Task Create(UserCreateViewModel model, CancellationToken cancellationToken) 150 | { 151 | if (ModelState.IsValid) 152 | { 153 | string message = ""; 154 | if (await _userService.IsExists("UserName", model.UserName, cancellationToken)) 155 | { 156 | message = $"The user name- '{model.UserName}' already exists"; 157 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 158 | { 159 | Success = false, 160 | Message = message, 161 | Error = new ErrorViewModel 162 | { 163 | Code = "DUPLICATE_NAME", 164 | Message = message 165 | } 166 | }); 167 | } 168 | 169 | if (await _userService.IsExists("Email", model.Email, cancellationToken)) 170 | { 171 | message = $"The user Email- '{model.Email}' already exists"; 172 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 173 | { 174 | Success = false, 175 | Message = message, 176 | Error = new ErrorViewModel 177 | { 178 | Code = "DUPLICATE_CODE", 179 | Message = message 180 | } 181 | }); 182 | } 183 | 184 | try 185 | { 186 | var response = await _userService.Create(model, cancellationToken); 187 | 188 | if (response.Success) 189 | { 190 | return Ok(response); 191 | } 192 | else 193 | { 194 | return BadRequest(response); 195 | } 196 | } 197 | catch (Exception ex) 198 | { 199 | _logger.LogError(ex, $"An error occurred while adding the user"); 200 | message = $"An error occurred while adding the user- " + ex.Message; 201 | 202 | return StatusCode(StatusCodes.Status500InternalServerError, new ResponseViewModel 203 | { 204 | Success = false, 205 | Message = message, 206 | Error = new ErrorViewModel 207 | { 208 | Code = "ADD_USER_ERROR", 209 | Message = message 210 | } 211 | }); 212 | } 213 | } 214 | 215 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 216 | { 217 | Success = false, 218 | Message = "Invalid input", 219 | Error = new ErrorViewModel 220 | { 221 | Code = "INPUT_VALIDATION_ERROR", 222 | Message = ModelStateHelper.GetErrors(ModelState) 223 | } 224 | }); 225 | } 226 | 227 | [HttpPut] 228 | public async Task Edit(UserUpdateViewModel model, CancellationToken cancellationToken) 229 | { 230 | if (ModelState.IsValid) 231 | { 232 | string message = ""; 233 | if (await _userService.IsExistsForUpdate(model.Id, "UserName", model.UserName, cancellationToken)) 234 | { 235 | message = $"The user name- '{model.UserName}' already exists"; 236 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 237 | { 238 | Success = false, 239 | Message = message, 240 | Error = new ErrorViewModel 241 | { 242 | Code = "DUPLICATE_NAME", 243 | Message = message 244 | } 245 | }); 246 | } 247 | 248 | if (await _userService.IsExistsForUpdate(model.Id, "Email", model.Email, cancellationToken)) 249 | { 250 | message = $"The user Email- '{model.Email}' already exists"; 251 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 252 | { 253 | Success = false, 254 | Message = message, 255 | Error = new ErrorViewModel 256 | { 257 | Code = "DUPLICATE_CODE", 258 | Message = message 259 | } 260 | }); 261 | } 262 | 263 | try 264 | { 265 | var response = await _userService.Update(model, cancellationToken); 266 | 267 | if (response.Success) 268 | { 269 | return Ok(response); 270 | } 271 | else 272 | { 273 | return BadRequest(response); 274 | } 275 | } 276 | catch (Exception ex) 277 | { 278 | _logger.LogError(ex, $"An error occurred while updating the user"); 279 | message = $"An error occurred while updating the user- " + ex.Message; 280 | 281 | return StatusCode(StatusCodes.Status500InternalServerError, new ResponseViewModel 282 | { 283 | Success = false, 284 | Message = message, 285 | Error = new ErrorViewModel 286 | { 287 | Code = "UPDATE_USER_ERROR", 288 | Message = message 289 | } 290 | }); 291 | } 292 | } 293 | 294 | return StatusCode(StatusCodes.Status400BadRequest, new ResponseViewModel 295 | { 296 | Success = false, 297 | Message = "Invalid input", 298 | Error = new ErrorViewModel 299 | { 300 | Code = "INPUT_VALIDATION_ERROR", 301 | Message = ModelStateHelper.GetErrors(ModelState) 302 | } 303 | }); 304 | } 305 | 306 | [HttpDelete("{id}")] 307 | public async Task Delete(int id, CancellationToken cancellationToken) 308 | { 309 | try 310 | { 311 | await _userService.Delete(id, cancellationToken); 312 | 313 | var response = new ResponseViewModel 314 | { 315 | Success = true, 316 | Message = "User deleted successfully" 317 | }; 318 | 319 | return Ok(response); 320 | } 321 | catch (Exception ex) 322 | { 323 | _logger.LogError(ex, "An error occurred while deleting the user"); 324 | 325 | var errorResponse = new ResponseViewModel 326 | { 327 | Success = false, 328 | Message = "Error deleting the user", 329 | Error = new ErrorViewModel 330 | { 331 | Code = "DELETE_USER_ERROR", 332 | Message = ex.Message 333 | } 334 | }; 335 | 336 | return StatusCode(StatusCodes.Status500InternalServerError, errorResponse); 337 | } 338 | } 339 | 340 | [HttpPost("reset-password")] 341 | public async Task ResetPassword([FromBody] ResetPasswordViewModel model) 342 | { 343 | if (!ModelState.IsValid) 344 | { 345 | return BadRequest(new ResponseViewModel 346 | { 347 | Success = false, 348 | Message = "Invalid input", 349 | Error = new ErrorViewModel 350 | { 351 | Code = "INVALID_INPUT", 352 | Message = ModelStateHelper.GetErrors(ModelState) 353 | 354 | } 355 | }); 356 | } 357 | 358 | var response = await _userService.ResetPassword(model); 359 | 360 | if (response.Success) 361 | { 362 | return Ok(response); 363 | } 364 | else 365 | { 366 | return BadRequest(response); 367 | } 368 | } 369 | 370 | } 371 | 372 | } 373 | -------------------------------------------------------------------------------- /Project.API/Extensions/ConfigureSwaggerOptions.cs: -------------------------------------------------------------------------------- 1 | using Asp.Versioning.ApiExplorer; 2 | using Microsoft.Extensions.Options; 3 | using Microsoft.OpenApi.Models; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | 6 | namespace Project.API.Extensions 7 | { 8 | public class ConfigureSwaggerOptions : IConfigureOptions 9 | { 10 | private readonly IApiVersionDescriptionProvider _provider; 11 | 12 | public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => _provider = provider; 13 | 14 | public void Configure(SwaggerGenOptions options) 15 | { 16 | foreach (var description in _provider.ApiVersionDescriptions) 17 | { 18 | options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); 19 | } 20 | } 21 | 22 | private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) 23 | { 24 | var info = new OpenApiInfo() 25 | { 26 | Title = "Web API Project", 27 | Version = description.ApiVersion.ToString(), 28 | Contact = new OpenApiContact { Name = "Kawser Hamid", Email = "kawser2133@gmail.com" }, 29 | }; 30 | 31 | if (description.IsDeprecated) 32 | { 33 | info.Description += " This API version has been deprecated."; 34 | } 35 | 36 | return info; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Project.API/Extensions/MapperExtension.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Project.Core.Entities.Business; 3 | using Project.Core.Entities.General; 4 | using Project.Core.Interfaces.IMapper; 5 | using Project.Core.Mapper; 6 | 7 | namespace Project.API.Extensions 8 | { 9 | public static class MapperExtension 10 | { 11 | public static IServiceCollection RegisterMapperService(this IServiceCollection services) 12 | { 13 | 14 | #region Mapper 15 | 16 | services.AddSingleton(sp => new MapperConfiguration(cfg => 17 | { 18 | cfg.CreateMap(); 19 | cfg.CreateMap(); 20 | cfg.CreateMap(); 21 | 22 | cfg.CreateMap(); 23 | cfg.CreateMap(); 24 | cfg.CreateMap(); 25 | 26 | cfg.CreateMap(); 27 | cfg.CreateMap(); 28 | 29 | }).CreateMapper()); 30 | 31 | // Register the IMapperService implementation with your dependency injection container 32 | services.AddSingleton, BaseMapper>(); 33 | services.AddSingleton, BaseMapper>(); 34 | services.AddSingleton, BaseMapper>(); 35 | 36 | services.AddSingleton, BaseMapper>(); 37 | services.AddSingleton, BaseMapper>(); 38 | services.AddSingleton, BaseMapper>(); 39 | 40 | services.AddSingleton, BaseMapper>(); 41 | services.AddSingleton, BaseMapper>(); 42 | 43 | #endregion 44 | 45 | return services; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Project.API/Extensions/SecurityExtension.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using Project.Infrastructure.Data; 3 | using Microsoft.AspNetCore.Authentication.JwtBearer; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.IdentityModel.Tokens; 6 | using System.Text; 7 | 8 | namespace Project.API.Extensions 9 | { 10 | public static class SecurityExtension 11 | { 12 | public static IServiceCollection RegisterSecurityService(this IServiceCollection services, IConfiguration configuration) 13 | { 14 | #region Identity 15 | services.AddIdentity(options => 16 | { 17 | options.Password.RequireDigit = true; 18 | options.Password.RequireLowercase = true; 19 | options.Password.RequiredLength = 6; 20 | }).AddEntityFrameworkStores() 21 | .AddDefaultTokenProviders(); 22 | 23 | #endregion 24 | 25 | #region JWT 26 | services.AddAuthentication(options => 27 | { 28 | //options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 29 | options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; 30 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 31 | }).AddJwtBearer(jwt => 32 | { 33 | var key = Encoding.ASCII.GetBytes(configuration["AppSettings:JwtConfig:Secret"]); 34 | jwt.SaveToken = true; 35 | jwt.TokenValidationParameters = new TokenValidationParameters 36 | { 37 | ValidateIssuerSigningKey = true, 38 | IssuerSigningKey = new SymmetricSecurityKey(key), 39 | ValidateIssuer = true, 40 | ValidateAudience = true, 41 | ValidAudience = configuration["AppSettings:JwtConfig:ValidAudience"], 42 | ValidIssuer = configuration["AppSettings:JwtConfig:ValidIssuer"], 43 | ValidateLifetime = true, 44 | RequireExpirationTime = true, 45 | ClockSkew = TimeSpan.Zero 46 | }; 47 | }); 48 | #endregion 49 | 50 | return services; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Project.API/Extensions/ServiceExtension.cs: -------------------------------------------------------------------------------- 1 | using Project.API.Middlewares; 2 | using Project.Core.Interfaces.IRepositories; 3 | using Project.Core.Interfaces.IServices; 4 | using Project.Core.Services; 5 | using Project.Infrastructure.Repositories; 6 | 7 | namespace Project.API.Extensions 8 | { 9 | public static class ServiceExtension 10 | { 11 | public static IServiceCollection RegisterService(this IServiceCollection services) 12 | { 13 | #region Services 14 | services.AddSingleton(); 15 | services.AddScoped(); 16 | services.AddScoped(); 17 | services.AddScoped(); 18 | services.AddScoped(); 19 | 20 | #endregion 21 | 22 | #region Repositories 23 | services.AddTransient(); 24 | services.AddTransient(); 25 | services.AddTransient(); 26 | services.AddTransient(); 27 | 28 | #endregion 29 | 30 | return services; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Project.API/Extensions/SwaggerDefaultValues.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | using Microsoft.OpenApi.Models; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | using System.Text.Json; 6 | 7 | namespace Project.API.Extensions 8 | { 9 | public class SwaggerDefaultValues : IOperationFilter 10 | { 11 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 12 | { 13 | var apiDescription = context.ApiDescription; 14 | operation.Deprecated |= apiDescription.IsDeprecated(); 15 | 16 | foreach (var responseType in context.ApiDescription.SupportedResponseTypes) 17 | { 18 | var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); 19 | var response = operation.Responses[responseKey]; 20 | 21 | foreach (var contentType in response.Content.Keys) 22 | { 23 | if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType)) 24 | { 25 | response.Content.Remove(contentType); 26 | } 27 | } 28 | } 29 | 30 | if (operation.Parameters == null) 31 | { 32 | return; 33 | } 34 | 35 | foreach (var parameter in operation.Parameters) 36 | { 37 | var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); 38 | 39 | parameter.Description ??= description.ModelMetadata?.Description; 40 | 41 | if (parameter.Schema.Default == null && 42 | description.DefaultValue != null && 43 | description.DefaultValue is not DBNull && 44 | description.ModelMetadata is ModelMetadata modelMetadata) 45 | { 46 | var json = JsonSerializer.Serialize(description.DefaultValue, modelMetadata.ModelType); 47 | parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json); 48 | } 49 | 50 | parameter.Required |= description.IsRequired; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Project.API/Helpers/ModelStateHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | 3 | namespace Project.API.Helpers 4 | { 5 | public static class ModelStateHelper 6 | { 7 | public static string GetErrors(ModelStateDictionary modelState) 8 | { 9 | var errors = modelState 10 | .Where(e => e.Value.Errors.Any()) 11 | .ToDictionary( 12 | kvp => kvp.Key, 13 | kvp => string.Join(", ", kvp.Value.Errors.Select(error => error.ErrorMessage)) 14 | ); 15 | 16 | return string.Join(", ", errors.Select(kvp => $"{kvp.Key}: {kvp.Value}")); 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Project.API/Middlewares/RequestResponseLoggingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Interfaces.IServices; 2 | using System.Security.Claims; 3 | using System.Text; 4 | 5 | namespace Project.API.Middlewares 6 | { 7 | public class RequestResponseLoggingMiddleware 8 | { 9 | private readonly RequestDelegate _next; 10 | private readonly ILogger _logger; 11 | private readonly IUserContext _userContext; 12 | 13 | public RequestResponseLoggingMiddleware( 14 | RequestDelegate next, 15 | ILogger logger, 16 | IUserContext userContext) 17 | { 18 | _next = next; 19 | _logger = logger; 20 | _userContext = userContext; 21 | } 22 | 23 | public async Task Invoke(HttpContext context) 24 | { 25 | // Log the incoming request 26 | LogRequest(context.Request); 27 | 28 | var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; 29 | _userContext.UserId = userId; 30 | 31 | // Call the next middleware in the pipeline 32 | await _next(context); 33 | 34 | // Log the outgoing response 35 | LogResponse(context.Response); 36 | } 37 | 38 | private void LogRequest(HttpRequest request) 39 | { 40 | _logger.LogInformation($"{_userContext.UserId} Request received: {request.Method} {request.Path}"); 41 | _logger.LogInformation($"{_userContext.UserId} Request headers: {GetHeadersAsString(request.Headers)}"); 42 | } 43 | 44 | private void LogResponse(HttpResponse response) 45 | { 46 | _logger.LogInformation($"{_userContext.UserId} Response sent: {response.StatusCode}"); 47 | _logger.LogInformation($"{_userContext.UserId} Response headers: {GetHeadersAsString(response.Headers)}"); 48 | } 49 | 50 | private string GetHeadersAsString(IHeaderDictionary headers) 51 | { 52 | var stringBuilder = new StringBuilder(); 53 | foreach (var (key, value) in headers) 54 | { 55 | stringBuilder.AppendLine($"{key}: {value}"); 56 | } 57 | return stringBuilder.ToString(); 58 | } 59 | } 60 | 61 | // Extension method used to add the middleware to the HTTP request pipeline. 62 | public static class RequestResponseLoggingMiddlewareExtensions 63 | { 64 | public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder) 65 | { 66 | return builder.UseMiddleware(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Project.API/Program.cs: -------------------------------------------------------------------------------- 1 | using Asp.Versioning; 2 | using Project.API.Extensions; 3 | using Project.API.Middlewares; 4 | using Project.Infrastructure.Data; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.Options; 7 | using Microsoft.OpenApi.Models; 8 | using Swashbuckle.AspNetCore.SwaggerGen; 9 | using Project.Core.Common; 10 | 11 | var builder = WebApplication.CreateBuilder(args); 12 | 13 | // Register DbContext 14 | builder.Services.AddDbContext(options => 15 | options.UseSqlServer(builder.Configuration.GetConnectionString("PrimaryDbConnection"))); 16 | builder.Services.Configure(builder.Configuration.GetSection("AppSettings")); 17 | 18 | // Add caching services 19 | builder.Services.AddMemoryCache(); 20 | 21 | // Register ILogger service 22 | builder.Services.AddLogging(loggingBuilder => 23 | { 24 | loggingBuilder.AddSeq(builder.Configuration.GetSection("SeqConfig")); 25 | }); 26 | 27 | // Register Services 28 | builder.Services.RegisterSecurityService(builder.Configuration); 29 | builder.Services.RegisterService(); 30 | builder.Services.RegisterMapperService(); 31 | builder.Services.AddAuthorization(); 32 | 33 | // API Versioning 34 | builder.Services 35 | .AddApiVersioning() 36 | .AddApiExplorer(options => 37 | { 38 | options.GroupNameFormat = "'v'VVV"; 39 | options.SubstituteApiVersionInUrl = true; 40 | options.DefaultApiVersion = new ApiVersion(1, 0); 41 | }); 42 | 43 | builder.Services.AddEndpointsApiExplorer(); 44 | builder.Services.AddControllers(); 45 | 46 | // Swagger Settings 47 | builder.Services.AddTransient, ConfigureSwaggerOptions>(); 48 | builder.Services.AddSwaggerGen(options => 49 | { 50 | options.OperationFilter(); 51 | 52 | // Define Bearer token security scheme 53 | options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme 54 | { 55 | Name = "Authorization", 56 | Description = "JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below. Example: 'Bearer 12345abcdef'", 57 | In = ParameterLocation.Header, 58 | Type = SecuritySchemeType.ApiKey, 59 | Scheme = "Bearer", 60 | BearerFormat = "JWT" 61 | }); 62 | 63 | // Add Bearer token as a requirement for all operations 64 | options.AddSecurityRequirement(new OpenApiSecurityRequirement 65 | { 66 | { 67 | new OpenApiSecurityScheme 68 | { 69 | Reference = new OpenApiReference 70 | { 71 | Type = ReferenceType.SecurityScheme, 72 | Id = "Bearer" 73 | } 74 | }, 75 | new string[] {} 76 | } 77 | }); 78 | }); 79 | 80 | var app = builder.Build(); 81 | 82 | // Database seeding 83 | using (var scope = app.Services.CreateScope()) 84 | { 85 | var services = scope.ServiceProvider; 86 | var loggerFactory = services.GetRequiredService(); 87 | 88 | try 89 | { 90 | var context = services.GetRequiredService(); 91 | 92 | // Seed the database 93 | await ApplicationDbContextSeed.SeedAsync(services, loggerFactory); 94 | } 95 | catch (Exception ex) 96 | { 97 | var logger = loggerFactory.CreateLogger(); 98 | logger.LogError(ex, "An error occurred while seeding the database."); 99 | } 100 | } 101 | 102 | if (app.Environment.IsDevelopment()) 103 | { 104 | app.UseSwagger(); 105 | app.UseSwaggerUI(options => 106 | { 107 | options.DefaultModelsExpandDepth(-1); 108 | var descriptions = app.DescribeApiVersions(); 109 | 110 | // Build a swagger endpoint for each discovered API version 111 | foreach (var description in descriptions) 112 | { 113 | var url = $"/swagger/{description.GroupName}/swagger.json"; 114 | var name = description.GroupName.ToUpperInvariant(); 115 | options.SwaggerEndpoint(url, name); 116 | } 117 | }); 118 | } 119 | 120 | app.UseStaticFiles(); 121 | app.UseRouting(); // Add this line to configure routing 122 | 123 | app.UseAuthentication(); 124 | app.UseAuthorization(); 125 | 126 | #region Custom Middleware 127 | app.UseMiddleware(); 128 | 129 | #endregion 130 | 131 | app.UseEndpoints(endpoints => 132 | { 133 | endpoints.MapControllers(); // Map your regular API controllers 134 | }); 135 | 136 | app.Run(); 137 | 138 | -------------------------------------------------------------------------------- /Project.API/Project.API.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Project.API/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:1200", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:1100", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Project.API/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Project.API/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "ConnectionStrings": { 9 | "PrimaryDbConnection": "server=DESKTOP-R2F3BLK; database=WebApi_Db; Trusted_Connection=True;TrustServerCertificate=True" 10 | }, 11 | "AppSettings": { 12 | "SeqConfig": { 13 | "ServerUrl": "http://localhost:5341", 14 | "ApiKey": "TLfE5FGnfmGjXatgs6xI", 15 | "MinimumLevel": "Trace", 16 | "LevelOverride": { 17 | "Microsoft": "Warning" 18 | } 19 | }, 20 | "JwtConfig": { 21 | "Secret": "kawkjf2342dla43kkjf23sda43ksdkjham", 22 | "ValidAudience": "http://localhost:1100", 23 | "ValidIssuer": "http://localhost:1100", 24 | "TokenExpirationMinutes": 30 25 | } 26 | }, 27 | "AllowedHosts": "*" 28 | } 29 | -------------------------------------------------------------------------------- /Project.Core/Common/AppSettings.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Project.Core.Common 3 | { 4 | public class AppSettings 5 | { 6 | public JwtConfig? JwtConfig { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Project.Core/Common/ExpressionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using System.Reflection; 3 | 4 | namespace Project.Core.Common 5 | { 6 | public static class ExpressionBuilder 7 | { 8 | public static Expression> ConstructAndExpressionTree(List filters) 9 | { 10 | if (filters.Count == 0) 11 | return null; 12 | 13 | ParameterExpression param = Expression.Parameter(typeof(T), "t"); 14 | Expression exp = null; 15 | 16 | if (filters.Count == 1) 17 | { 18 | exp = GetExpression(param, filters[0]); 19 | } 20 | else 21 | { 22 | exp = GetExpression(param, filters[0]); 23 | for (int i = 1; i < filters.Count; i++) 24 | { 25 | exp = Expression.Or(exp, GetExpression(param, filters[i])); 26 | } 27 | } 28 | 29 | return Expression.Lambda>(exp, param); 30 | } 31 | 32 | public static Expression GetExpression(ParameterExpression param, ExpressionFilter filter) 33 | { 34 | MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string) }); 35 | MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }); 36 | MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }); 37 | 38 | MemberExpression member = Expression.Property(param, filter.PropertyName); 39 | ConstantExpression constant = Expression.Constant(filter.Value); 40 | 41 | switch (filter.Comparison) 42 | { 43 | case Comparison.Equal: 44 | return Expression.Equal(member, constant); 45 | case Comparison.GreaterThan: 46 | return Expression.GreaterThan(member, constant); 47 | case Comparison.GreaterThanOrEqual: 48 | return Expression.GreaterThanOrEqual(member, constant); 49 | case Comparison.LessThan: 50 | return Expression.LessThan(member, constant); 51 | case Comparison.LessThanOrEqual: 52 | return Expression.LessThanOrEqual(member, constant); 53 | case Comparison.NotEqual: 54 | return Expression.NotEqual(member, constant); 55 | case Comparison.Contains: 56 | return Expression.Call(member, containsMethod, constant); 57 | case Comparison.StartsWith: 58 | return Expression.Call(member, startsWithMethod, constant); 59 | case Comparison.EndsWith: 60 | return Expression.Call(member, endsWithMethod, constant); 61 | default: 62 | return null; 63 | } 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Project.Core/Common/ExpressionFilter.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Project.Core.Common 4 | { 5 | public class ExpressionFilter 6 | { 7 | public string? PropertyName { get; set; } 8 | public object? Value { get; set; } 9 | public Comparison Comparison { get; set; } 10 | } 11 | 12 | public enum Comparison 13 | { 14 | [Display(Name = "==")] 15 | Equal, 16 | 17 | [Display(Name = "<")] 18 | LessThan, 19 | 20 | [Display(Name = "<=")] 21 | LessThanOrEqual, 22 | 23 | [Display(Name = ">")] 24 | GreaterThan, 25 | 26 | [Display(Name = ">=")] 27 | GreaterThanOrEqual, 28 | 29 | [Display(Name = "!=")] 30 | NotEqual, 31 | 32 | [Display(Name = "Contains")] 33 | Contains, //for strings 34 | 35 | [Display(Name = "StartsWith")] 36 | StartsWith, //for strings 37 | 38 | [Display(Name = "EndsWith")] 39 | EndsWith, //for strings 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Project.Core/Common/JwtConfig.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Project.Core.Common 3 | { 4 | public class JwtConfig 5 | { 6 | public string? Secret { get; set; } 7 | public string? ValidAudience { get; set; } 8 | public string? ValidIssuer { get; set; } 9 | public int TokenExpirationMinutes { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/AuthResultViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Project.Core.Entities.Business 3 | { 4 | public class AuthResultViewModel 5 | { 6 | public string? AccessToken { get; set; } 7 | public bool Success { get; set; } 8 | public List? Errors { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Project.Core.Entities.Business 3 | { 4 | public class ErrorViewModel 5 | { 6 | public string? Code { get; set; } 7 | public string? Message { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Project.Core.Entities.Business 4 | { 5 | public class LoginViewModel 6 | { 7 | [Required, StringLength(20, MinimumLength = 2)] 8 | public string? UserName { get; set; } 9 | 10 | [Required, StringLength(50, MinimumLength = 6)] 11 | [DataType(DataType.Password)] 12 | public string? Password { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/PaginatedDataViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Project.Core.Entities.Business 3 | { 4 | public class PaginatedDataViewModel 5 | { 6 | public IEnumerable Data { get; set; } 7 | public int TotalCount { get; set; } 8 | 9 | public PaginatedDataViewModel(IEnumerable data, int totalCount) 10 | { 11 | Data = data; 12 | TotalCount = totalCount; 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/ProductViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Project.Core.Entities.Business 4 | { 5 | public class ProductViewModel 6 | { 7 | public int Id { get; set; } 8 | public string? Code { get; set; } 9 | public string? Name { get; set; } 10 | public double Price { get; set; } 11 | public int Quantity { get; set; } 12 | public string? Description { get; set; } 13 | public bool IsActive { get; set; } 14 | } 15 | 16 | public class ProductCreateViewModel 17 | { 18 | [Required, StringLength(maximumLength: 8, MinimumLength = 2)] 19 | public string? Code { get; set; } 20 | [Required, StringLength(maximumLength: 100, MinimumLength = 2)] 21 | public string? Name { get; set; } 22 | [Required, Range(0.01, float.MaxValue)] 23 | public double Price { get; set; } 24 | public int Quantity { get; set; } 25 | [StringLength(maximumLength: 350)] 26 | public string? Description { get; set; } 27 | public bool IsActive { get; set; } 28 | } 29 | 30 | public class ProductUpdateViewModel 31 | { 32 | public int Id { get; set; } 33 | [Required, StringLength(maximumLength: 8, MinimumLength = 2)] 34 | public string? Code { get; set; } 35 | [Required, StringLength(maximumLength: 100, MinimumLength = 2)] 36 | public string? Name { get; set; } 37 | [Required, Range(0.01, float.MaxValue)] 38 | public double Price { get; set; } 39 | public int Quantity { get; set; } 40 | [StringLength(maximumLength: 350)] 41 | public string? Description { get; set; } 42 | public bool IsActive { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/ResetPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Project.Core.Entities.Business 4 | { 5 | public class ResetPasswordViewModel 6 | { 7 | [Required, StringLength(20, MinimumLength = 2)] 8 | public string? UserName { get; set; } 9 | 10 | [Required, StringLength(50, MinimumLength = 6)] 11 | [DataType(DataType.Password)] 12 | public string? NewPassword { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/ResponseViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Project.Core.Entities.Business 3 | { 4 | public class ResponseViewModel 5 | { 6 | public bool Success { get; set; } 7 | public string? Message { get; set; } 8 | public T? Data { get; set; } 9 | public ErrorViewModel? Error { get; set; } 10 | } 11 | 12 | public class ResponseViewModel 13 | { 14 | public bool Success { get; set; } 15 | public string? Message { get; set; } 16 | public ErrorViewModel? Error { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/RoleViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Project.Core.Entities.Business 4 | { 5 | public class RoleViewModel 6 | { 7 | public int Id { get; set; } 8 | public string? Code { get; set; } 9 | public string? Name { get; set; } 10 | public bool IsActive { get; set; } 11 | } 12 | 13 | public class RoleCreateViewModel 14 | { 15 | [Required, StringLength(maximumLength: 10, MinimumLength = 2)] 16 | public string? Code { get; set; } 17 | 18 | [Required, StringLength(100, MinimumLength = 2)] 19 | public string? Name { get; set; } 20 | 21 | public bool IsActive { get; set; } 22 | } 23 | 24 | public class RoleUpdateViewModel 25 | { 26 | public int Id { get; set; } 27 | 28 | [Required, StringLength(maximumLength: 10, MinimumLength = 2)] 29 | public string? Code { get; set; } 30 | 31 | [Required, StringLength(100, MinimumLength = 2)] 32 | public string? Name { get; set; } 33 | public bool IsActive { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Project.Core/Entities/Business/UserViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Project.Core.Entities.Business 4 | { 5 | public class UserViewModel 6 | { 7 | public int Id { get; set; } 8 | public string? FullName { get; set; } 9 | public string? UserName { get; set; } 10 | public string? Email { get; set; } 11 | public string? Role { get; set; } 12 | } 13 | 14 | public class UserCreateViewModel 15 | { 16 | [Required, StringLength(100, MinimumLength = 2)] 17 | public string? FullName { get; set; } 18 | 19 | [Required, StringLength(20, MinimumLength = 2)] 20 | public string? UserName { get; set; } 21 | 22 | [Required, EmailAddress] 23 | public string? Email { get; set; } 24 | 25 | [Required, StringLength(50, MinimumLength = 6)] 26 | [DataType(DataType.Password)] 27 | public string? Password { get; set; } 28 | 29 | [Required] 30 | public int RoleId { get; set; } 31 | } 32 | 33 | public class UserUpdateViewModel 34 | { 35 | public int Id { get; set; } 36 | 37 | [Required, StringLength(100, MinimumLength = 2)] 38 | public string? FullName { get; set; } 39 | 40 | [Required, StringLength(20, MinimumLength = 2)] 41 | public string? UserName { get; set; } 42 | 43 | [Required, EmailAddress] 44 | public string? Email { get; set; } 45 | 46 | [Required] 47 | public int RoleId { get; set; } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Project.Core/Entities/General/Base.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Project.Core.Entities.General 4 | { 5 | //Base class for entities common properties 6 | public class Base 7 | { 8 | [Key] 9 | public T? Id { get; set; } 10 | public int? EntryBy { get; set; } 11 | public DateTime? EntryDate { get; set; } 12 | public int? UpdatedBy { get; set; } 13 | public DateTime? UpdatedDate { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Project.Core/Entities/General/Product.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace Project.Core.Entities.General 5 | { 6 | [Table("Products")] 7 | public class Product : Base 8 | { 9 | [Required, StringLength(maximumLength: 8, MinimumLength = 2)] 10 | public string Code { get; set; } 11 | [Required, StringLength(maximumLength: 100, MinimumLength = 2)] 12 | public string Name { get; set; } 13 | [Required] 14 | public double Price { get; set; } 15 | public int Quantity { get; set; } 16 | [StringLength(maximumLength: 350)] 17 | public string? Description { get; set; } 18 | public bool IsActive { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Project.Core/Entities/General/Role.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Project.Core.Entities.General 5 | { 6 | public class Role : IdentityRole 7 | { 8 | [Required, StringLength(maximumLength: 10, MinimumLength = 2)] 9 | public string Code { get; set; } 10 | public bool IsActive { get; set; } 11 | public int? EntryBy { get; set; } 12 | public DateTime? EntryDate { get; set; } 13 | public int? UpdatedBy { get; set; } 14 | public DateTime? UpdatedDate { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Project.Core/Entities/General/User.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Project.Core.Entities.General 6 | { 7 | public class User : IdentityUser 8 | { 9 | [Required, StringLength(maximumLength: 100, MinimumLength = 2)] 10 | public string FullName { get; set; } 11 | public bool IsActive { get; set; } 12 | [Required] 13 | public int RoleId { get; set; } 14 | public int? EntryBy { get; set; } 15 | public DateTime? EntryDate { get; set; } 16 | public int? UpdatedBy { get; set; } 17 | public DateTime? UpdatedDate { get; set; } 18 | 19 | [ForeignKey(nameof(RoleId))] 20 | public Role Role { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /Project.Core/Exceptions/NotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace Project.Core.Exceptions 4 | { 5 | public class NotFoundException : Exception 6 | { 7 | public NotFoundException() 8 | { 9 | } 10 | 11 | public NotFoundException(string? message) : base(message) 12 | { 13 | } 14 | 15 | public NotFoundException(string? message, Exception? innerException) : base(message, innerException) 16 | { 17 | } 18 | 19 | protected NotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) 20 | { 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IMapper/IBaseMapper.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Project.Core.Interfaces.IMapper 3 | { 4 | public interface IBaseMapper 5 | { 6 | TDestination MapModel(TSource source); 7 | TDestination MapModel(TSource source, TDestination destination); 8 | IEnumerable MapList(IEnumerable source); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/IAuthRepository.cs: -------------------------------------------------------------------------------- 1 |  2 | using Project.Core.Entities.Business; 3 | using Microsoft.AspNetCore.Identity; 4 | 5 | namespace Project.Core.Interfaces.IRepositories 6 | { 7 | public interface IAuthRepository 8 | { 9 | 10 | Task> Login(string userName, string password); 11 | Task Logout(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/IBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Common; 2 | using Project.Core.Entities.Business; 3 | using System.Linq.Expressions; 4 | 5 | namespace Project.Core.Interfaces.IRepositories 6 | { 7 | //Unit of Work Pattern 8 | public interface IBaseRepository where T : class 9 | { 10 | Task> GetAll(CancellationToken cancellationToken); 11 | Task> GetAll(List>> includeExpressions, CancellationToken cancellationToken = default); 12 | Task> GetPaginatedData(int pageNumber, int pageSize, CancellationToken cancellationToken); 13 | Task> GetPaginatedData(int pageNumber, int pageSize, List filters, CancellationToken cancellationToken); 14 | Task> GetPaginatedData(int pageNumber, int pageSize, List filters, string sortBy, string sortOrder, CancellationToken cancellationToken); 15 | Task> GetPaginatedData(List>> includeExpressions, int pageNumber, int pageSize, CancellationToken cancellationToken); 16 | Task GetById(Tid id, CancellationToken cancellationToken); 17 | Task GetById(List>> includeExpressions, Tid id, CancellationToken cancellationToken); 18 | Task IsExists(string key, Tvalue value, CancellationToken cancellationToken); 19 | Task IsExistsForUpdate(Tid id, string key, string value, CancellationToken cancellationToken); 20 | Task Create(T model, CancellationToken cancellationToken); 21 | Task CreateRange(List model, CancellationToken cancellationToken); 22 | Task Update(T model, CancellationToken cancellationToken); 23 | Task Delete(T model, CancellationToken cancellationToken); 24 | Task SaveChangeAsync(CancellationToken cancellationToken); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/IProductRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | 3 | namespace Project.Core.Interfaces.IRepositories 4 | { 5 | public interface IProductRepository : IBaseRepository 6 | { 7 | Task PriceCheck(int productId, CancellationToken cancellationToken); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/IRoleRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | 3 | namespace Project.Core.Interfaces.IRepositories 4 | { 5 | public interface IRoleRepository : IBaseRepository 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IRepositories/IUserRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Microsoft.AspNetCore.Identity; 4 | 5 | namespace Project.Core.Interfaces.IRepositories 6 | { 7 | public interface IUserRepository : IBaseRepository 8 | { 9 | Task Create(UserCreateViewModel model); 10 | Task Update(UserUpdateViewModel model); 11 | Task ResetPassword(ResetPasswordViewModel model); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IServices/IAuthService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | 3 | namespace Project.Core.Interfaces.IServices 4 | { 5 | public interface IAuthService 6 | { 7 | Task> Login(string userName, string password); 8 | Task Logout(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IServices/IBaseService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Common; 2 | using Project.Core.Entities.Business; 3 | 4 | namespace Project.Core.Interfaces.IServices 5 | { 6 | public interface IBaseService 7 | where TViewModel : class 8 | { 9 | Task> GetAll(CancellationToken cancellationToken); 10 | Task> GetPaginatedData(int pageNumber, int pageSize, CancellationToken cancellationToken); 11 | Task> GetPaginatedData(int pageNumber, int pageSize, List filters, CancellationToken cancellationToken); 12 | Task> GetPaginatedData(int pageNumber, int pageSize, List filters, string sortBy, string sortOrder, CancellationToken cancellationToken); 13 | Task GetById(Tid id, CancellationToken cancellationToken); 14 | Task IsExists(string key, Tvalue value, CancellationToken cancellationToken); 15 | Task IsExistsForUpdate(Tid id, string key, string value, CancellationToken cancellationToken); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IServices/IProductService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | 4 | namespace Project.Core.Interfaces.IServices 5 | { 6 | public interface IProductService : IBaseService 7 | { 8 | Task Create(ProductCreateViewModel model, CancellationToken cancellationToken); 9 | Task Update(ProductUpdateViewModel model, CancellationToken cancellationToken); 10 | Task Delete(int id, CancellationToken cancellationToken); 11 | Task PriceCheck(int productId, CancellationToken cancellationToken); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IServices/IRoleService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | 3 | namespace Project.Core.Interfaces.IServices 4 | { 5 | public interface IRoleService : IBaseService 6 | { 7 | Task Create(RoleCreateViewModel model, CancellationToken cancellationToken); 8 | Task Update(RoleUpdateViewModel model, CancellationToken cancellationToken); 9 | Task Delete(int id, CancellationToken cancellationToken); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IServices/IUserContext.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Project.Core.Interfaces.IServices 3 | { 4 | public interface IUserContext 5 | { 6 | string UserId { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Project.Core/Interfaces/IServices/IUserService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | 3 | namespace Project.Core.Interfaces.IServices 4 | { 5 | public interface IUserService : IBaseService 6 | { 7 | new Task> GetAll(CancellationToken cancellationToken); 8 | new Task> GetPaginatedData(int pageNumber, int pageSize, CancellationToken cancellationToken); 9 | Task GetById(int id, CancellationToken cancellationToken); 10 | Task Create(UserCreateViewModel model, CancellationToken cancellationToken); 11 | Task Update(UserUpdateViewModel model, CancellationToken cancellationToken); 12 | Task Delete(int id, CancellationToken cancellationToken); 13 | Task ResetPassword(ResetPasswordViewModel model); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Project.Core/Mapper/BaseMapper.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Project.Core.Interfaces.IMapper; 3 | 4 | namespace Project.Core.Mapper 5 | { 6 | public class BaseMapper : IBaseMapper 7 | { 8 | private readonly IMapper _mapper; 9 | 10 | public BaseMapper(IMapper mapper) 11 | { 12 | _mapper = mapper; 13 | } 14 | 15 | public TDestination MapModel(TSource source) 16 | { 17 | return _mapper.Map(source); 18 | } 19 | 20 | public TDestination MapModel(TSource source, TDestination destination) 21 | { 22 | return _mapper.Map(source, destination); 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 | 15 | -------------------------------------------------------------------------------- /Project.Core/Services/AuthService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IMapper; 4 | using Project.Core.Interfaces.IRepositories; 5 | using Project.Core.Interfaces.IServices; 6 | 7 | namespace Project.Core.Services 8 | { 9 | public class AuthService : IAuthService 10 | { 11 | private readonly IAuthRepository _authRepository; 12 | 13 | public AuthService(IAuthRepository authRepository) 14 | { 15 | _authRepository = authRepository; 16 | } 17 | 18 | public async Task> Login(string userName, string password) 19 | { 20 | var result = await _authRepository.Login(userName, password); 21 | if (result.Success) 22 | { 23 | return new ResponseViewModel 24 | { 25 | Success = true, 26 | Message = "Login successful", 27 | Data = result.Data 28 | }; 29 | } 30 | else 31 | { 32 | return new ResponseViewModel 33 | { 34 | Success = false, 35 | Message = "Login failed", 36 | Error = new ErrorViewModel 37 | { 38 | Code = "LOGIN_ERROR", 39 | Message = "Incorrect username or password. Please check your credentials and try again." 40 | } 41 | }; 42 | } 43 | } 44 | 45 | public async Task Logout() 46 | { 47 | await _authRepository.Logout(); 48 | } 49 | 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Project.Core/Services/BaseService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Common; 2 | using Project.Core.Entities.Business; 3 | using Project.Core.Interfaces.IMapper; 4 | using Project.Core.Interfaces.IRepositories; 5 | using Project.Core.Interfaces.IServices; 6 | using System.Linq.Expressions; 7 | 8 | namespace Project.Core.Services 9 | { 10 | public class BaseService : IBaseService 11 | where T : class 12 | where TViewModel : class 13 | { 14 | private readonly IBaseMapper _viewModelMapper; 15 | private readonly IBaseRepository _repository; 16 | 17 | public BaseService( 18 | IBaseMapper viewModelMapper, 19 | IBaseRepository repository) 20 | { 21 | _viewModelMapper = viewModelMapper; 22 | _repository = repository; 23 | } 24 | 25 | public virtual async Task> GetAll(CancellationToken cancellationToken) 26 | { 27 | return _viewModelMapper.MapList(await _repository.GetAll(cancellationToken)); 28 | } 29 | 30 | public virtual async Task> GetPaginatedData(int pageNumber, int pageSize, CancellationToken cancellationToken) 31 | { 32 | var paginatedData = await _repository.GetPaginatedData(pageNumber, pageSize, cancellationToken); 33 | var mappedData = _viewModelMapper.MapList(paginatedData.Data); 34 | var paginatedDataViewModel = new PaginatedDataViewModel(mappedData.ToList(), paginatedData.TotalCount); 35 | return paginatedDataViewModel; 36 | } 37 | 38 | public virtual async Task> GetPaginatedData(int pageNumber, int pageSize, List filters, CancellationToken cancellationToken) 39 | { 40 | var paginatedData = await _repository.GetPaginatedData(pageNumber, pageSize, filters, cancellationToken); 41 | var mappedData = _viewModelMapper.MapList(paginatedData.Data); 42 | var paginatedDataViewModel = new PaginatedDataViewModel(mappedData.ToList(), paginatedData.TotalCount); 43 | return paginatedDataViewModel; 44 | } 45 | 46 | public virtual async Task> GetPaginatedData(int pageNumber, int pageSize, List filters, string sortBy, string sortOrder, CancellationToken cancellationToken) 47 | { 48 | var paginatedData = await _repository.GetPaginatedData(pageNumber, pageSize, filters, sortBy, sortOrder, cancellationToken); 49 | var mappedData = _viewModelMapper.MapList(paginatedData.Data); 50 | var paginatedDataViewModel = new PaginatedDataViewModel(mappedData.ToList(), paginatedData.TotalCount); 51 | return paginatedDataViewModel; 52 | } 53 | 54 | public virtual async Task GetById(Tid id, CancellationToken cancellationToken) 55 | { 56 | return _viewModelMapper.MapModel(await _repository.GetById(id, cancellationToken)); 57 | } 58 | 59 | public virtual async Task IsExists(string key, Tvalue value, CancellationToken cancellationToken) 60 | { 61 | return await _repository.IsExists(key, value?.ToString(), cancellationToken); 62 | } 63 | 64 | public virtual async Task IsExistsForUpdate(Tid id, string key, string value, CancellationToken cancellationToken) 65 | { 66 | return await _repository.IsExistsForUpdate(id, key, value, cancellationToken); 67 | } 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /Project.Core/Services/ProductService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IMapper; 4 | using Project.Core.Interfaces.IRepositories; 5 | using Project.Core.Interfaces.IServices; 6 | 7 | namespace Project.Core.Services 8 | { 9 | public class ProductService : BaseService, IProductService 10 | { 11 | private readonly IBaseMapper _productViewModelMapper; 12 | private readonly IBaseMapper _productCreateMapper; 13 | private readonly IBaseMapper _productUpdateMapper; 14 | private readonly IProductRepository _productRepository; 15 | private readonly IUserContext _userContext; 16 | 17 | public ProductService( 18 | IBaseMapper productViewModelMapper, 19 | IBaseMapper productCreateMapper, 20 | IBaseMapper productUpdateMapper, 21 | IProductRepository productRepository, 22 | IUserContext userContext) 23 | : base(productViewModelMapper, productRepository) 24 | { 25 | _productCreateMapper = productCreateMapper; 26 | _productUpdateMapper = productUpdateMapper; 27 | _productViewModelMapper = productViewModelMapper; 28 | _productRepository = productRepository; 29 | _userContext = userContext; 30 | } 31 | 32 | public async Task Create(ProductCreateViewModel model, CancellationToken cancellationToken) 33 | { 34 | //Mapping through AutoMapper 35 | var entity = _productCreateMapper.MapModel(model); 36 | entity.EntryDate = DateTime.Now; 37 | entity.EntryBy = Convert.ToInt32(_userContext.UserId); 38 | 39 | return _productViewModelMapper.MapModel(await _productRepository.Create(entity, cancellationToken)); 40 | } 41 | 42 | public async Task Update(ProductUpdateViewModel model, CancellationToken cancellationToken) 43 | { 44 | var existingData = await _productRepository.GetById(model.Id, cancellationToken); 45 | 46 | //Mapping through AutoMapper 47 | _productUpdateMapper.MapModel(model, existingData); 48 | 49 | // Set additional properties or perform other logic as needed 50 | existingData.UpdatedDate = DateTime.Now; 51 | existingData.UpdatedBy = Convert.ToInt32(_userContext.UserId); 52 | 53 | await _productRepository.Update(existingData, cancellationToken); 54 | } 55 | 56 | public async Task Delete(int id, CancellationToken cancellationToken) 57 | { 58 | var entity = await _productRepository.GetById(id, cancellationToken); 59 | await _productRepository.Delete(entity, cancellationToken); 60 | } 61 | 62 | public async Task PriceCheck(int productId, CancellationToken cancellationToken) 63 | { 64 | return await _productRepository.PriceCheck(productId, cancellationToken); 65 | } 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Project.Core/Services/RoleService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IMapper; 4 | using Project.Core.Interfaces.IRepositories; 5 | using Project.Core.Interfaces.IServices; 6 | 7 | namespace Project.Core.Services 8 | { 9 | public class RoleService : BaseService, IRoleService 10 | { 11 | private readonly IBaseMapper _roleViewModelMapper; 12 | private readonly IBaseMapper _roleCreateMapper; 13 | private readonly IBaseMapper _roleUpdateMapper; 14 | private readonly IRoleRepository _roleRepository; 15 | private readonly IUserContext _userContext; 16 | 17 | public RoleService( 18 | IBaseMapper roleViewModelMapper, 19 | IBaseMapper roleCreateMapper, 20 | IBaseMapper roleUpdateMapper, 21 | IRoleRepository roleRepository, 22 | IUserContext userContext) 23 | : base(roleViewModelMapper, roleRepository) 24 | { 25 | _roleCreateMapper = roleCreateMapper; 26 | _roleUpdateMapper = roleUpdateMapper; 27 | _roleViewModelMapper = roleViewModelMapper; 28 | _roleRepository = roleRepository; 29 | _userContext = userContext; 30 | } 31 | 32 | public async Task Create(RoleCreateViewModel model, CancellationToken cancellationToken) 33 | { 34 | //Mapping through AutoMapper 35 | var entity = _roleCreateMapper.MapModel(model); 36 | 37 | // Set additional properties or perform other logic as needed 38 | entity.NormalizedName = model.Name.ToUpper(); 39 | entity.EntryDate = DateTime.Now; 40 | entity.EntryBy = Convert.ToInt32(_userContext.UserId); 41 | 42 | return _roleViewModelMapper.MapModel(await _roleRepository.Create(entity, cancellationToken)); 43 | } 44 | 45 | public async Task Update(RoleUpdateViewModel model, CancellationToken cancellationToken) 46 | { 47 | var existingData = await _roleRepository.GetById(model.Id, cancellationToken); 48 | 49 | //Mapping through AutoMapper 50 | _roleUpdateMapper.MapModel(model, existingData); 51 | 52 | // Set additional properties or perform other logic as needed 53 | existingData.UpdatedDate = DateTime.Now; 54 | existingData.UpdatedBy = Convert.ToInt32(_userContext.UserId); 55 | 56 | await _roleRepository.Update(existingData, cancellationToken); 57 | } 58 | 59 | public async Task Delete(int id, CancellationToken cancellationToken) 60 | { 61 | var entity = await _roleRepository.GetById(id, cancellationToken); 62 | await _roleRepository.Delete(entity, cancellationToken); 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Project.Core/Services/UserContext.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Interfaces.IServices; 2 | 3 | namespace Project.Core.Services 4 | { 5 | public class UserContext : IUserContext 6 | { 7 | public string UserId { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Project.Core/Services/UserService.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IMapper; 4 | using Project.Core.Interfaces.IRepositories; 5 | using Project.Core.Interfaces.IServices; 6 | using System.Linq.Expressions; 7 | 8 | namespace Project.Core.Services 9 | { 10 | public class UserService : BaseService, IUserService 11 | { 12 | private readonly IBaseMapper _userViewModelMapper; 13 | private readonly IUserRepository _userRepository; 14 | 15 | public UserService( 16 | IBaseMapper userViewModelMapper, 17 | IUserRepository userRepository) 18 | : base(userViewModelMapper, userRepository) 19 | { 20 | _userViewModelMapper = userViewModelMapper; 21 | _userRepository = userRepository; 22 | } 23 | 24 | public new async Task> GetAll(CancellationToken cancellationToken) 25 | { 26 | var includeList = new List>> { x => x.Role }; 27 | var entities = await _userRepository.GetAll(includeList, cancellationToken); 28 | 29 | return _userViewModelMapper.MapList(entities); 30 | } 31 | 32 | public new async Task> GetPaginatedData(int pageNumber, int pageSize, CancellationToken cancellationToken) 33 | { 34 | var includeList = new List>> { x => x.Role }; 35 | 36 | var paginatedData = await _userRepository.GetPaginatedData(includeList, pageNumber, pageSize, cancellationToken); 37 | var mappedData = _userViewModelMapper.MapList(paginatedData.Data); 38 | var paginatedDataViewModel = new PaginatedDataViewModel(mappedData.ToList(), paginatedData.TotalCount); 39 | return paginatedDataViewModel; 40 | } 41 | 42 | public async Task GetById(int id, CancellationToken cancellationToken) 43 | { 44 | var includeList = new List>> { x => x.Role }; 45 | 46 | return _userViewModelMapper.MapModel(await _userRepository.GetById(includeList, id, cancellationToken)); 47 | } 48 | 49 | public async Task Create(UserCreateViewModel model, CancellationToken cancellationToken) 50 | { 51 | var result = await _userRepository.Create(model); 52 | if (result.Succeeded) 53 | { 54 | return new ResponseViewModel { Success = true, Message = "User created successfully" }; 55 | } 56 | else 57 | { 58 | return new ResponseViewModel 59 | { 60 | Success = false, 61 | Message = "User creation failed", 62 | Error = new ErrorViewModel 63 | { 64 | Code = "USER_CREATION_ERROR", 65 | Message = string.Join(", ", result.Errors.Select(e => e.Description)) 66 | } 67 | }; 68 | } 69 | } 70 | 71 | public async Task Update(UserUpdateViewModel model, CancellationToken cancellationToken) 72 | { 73 | var result = await _userRepository.Update(model); 74 | if (result.Succeeded) 75 | { 76 | return new ResponseViewModel { Success = true, Message = "User updated successfully" }; 77 | } 78 | else 79 | { 80 | return new ResponseViewModel 81 | { 82 | Success = false, 83 | Message = "User update failed", 84 | Error = new ErrorViewModel 85 | { 86 | Code = "USER_UPDATE_ERROR", 87 | Message = string.Join(", ", result.Errors.Select(e => e.Description)) 88 | } 89 | }; 90 | } 91 | } 92 | 93 | public async Task ResetPassword(ResetPasswordViewModel model) 94 | { 95 | var result = await _userRepository.ResetPassword(model); 96 | if (result.Succeeded) 97 | { 98 | return new ResponseViewModel { Success = true, Message = "Password reset successfully" }; 99 | } 100 | else 101 | { 102 | return new ResponseViewModel 103 | { 104 | Success = false, 105 | Message = "Password reset failed", 106 | Error = new ErrorViewModel 107 | { 108 | Code = "PASSWORD_RESET_ERROR", 109 | Message = string.Join(", ", result.Errors.Select(e => e.Description)) 110 | } 111 | }; 112 | } 113 | } 114 | 115 | public async Task Delete(int id, CancellationToken cancellationToken) 116 | { 117 | var entity = await _userRepository.GetById(id, cancellationToken); 118 | await _userRepository.Delete(entity, cancellationToken); 119 | } 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Project.Infrastructure/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace Project.Infrastructure.Data 6 | { 7 | public class ApplicationDbContext : IdentityDbContext 8 | { 9 | public ApplicationDbContext(DbContextOptions options) : base(options) 10 | { 11 | } 12 | 13 | #region DbSet Section 14 | public DbSet Products { get; set; } 15 | 16 | #endregion 17 | 18 | protected override void OnModelCreating(ModelBuilder builder) 19 | { 20 | base.OnModelCreating(builder); 21 | 22 | ApplicationDbContextConfigurations.Configure(builder); 23 | } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Project.Infrastructure/Data/ApplicationDbContextConfigurations.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace Project.Infrastructure.Data 6 | { 7 | public class ApplicationDbContextConfigurations 8 | { 9 | public static void Configure(ModelBuilder builder) 10 | { 11 | // Configure custom entities 12 | builder.Entity().ToTable("Users"); 13 | builder.Entity().ToTable("Roles"); 14 | 15 | // Configure Identity entities 16 | builder.Entity>().ToTable("UserRoles").HasKey(p => new { p.UserId, p.RoleId }); 17 | builder.Entity>().ToTable("UserClaims"); 18 | builder.Entity>().ToTable("UserLogins").HasKey(p => new { p.LoginProvider, p.ProviderKey }); 19 | builder.Entity>().ToTable("UserTokens").HasKey(p => new { p.UserId, p.LoginProvider, p.Name }); 20 | builder.Entity>().ToTable("RoleClaims"); 21 | 22 | // Add any additional entity configurations here 23 | 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Project.Infrastructure/Data/ApplicationDbContextSeed.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using Project.Core.Entities.General; 6 | 7 | namespace Project.Infrastructure.Data 8 | { 9 | public class ApplicationDbContextSeed 10 | { 11 | public static async Task SeedAsync(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, int? retry = 0) 12 | { 13 | int retryForAvailability = retry ?? 0; 14 | var appContext = serviceProvider.GetRequiredService(); 15 | var UserManager = serviceProvider.GetRequiredService>(); 16 | try 17 | { 18 | // Adding Roles 19 | if (!appContext.Roles.Any()) 20 | { 21 | using (var transaction = appContext.Database.BeginTransaction()) 22 | { 23 | appContext.Roles.AddRange(Roles()); 24 | await appContext.SaveChangesAsync(); 25 | transaction.Commit(); 26 | } 27 | } 28 | 29 | // Adding Users 30 | if (!appContext.Users.Any()) 31 | { 32 | var defaultUser = new User { FullName = "Kawser Hamid", UserName = "hamid", RoleId = 1, Email = "kawser2133@gmail.com", EntryDate = DateTime.Now, IsActive = true }; 33 | IdentityResult userResult = await UserManager.CreateAsync(defaultUser, "Hamid@12"); 34 | if (userResult.Succeeded) 35 | { 36 | // here we assign the new user role 37 | await UserManager.AddToRoleAsync(defaultUser, "ADMIN"); 38 | } 39 | } 40 | 41 | // Adding Products 42 | if (!appContext.Products.Any()) 43 | { 44 | using (var transaction = appContext.Database.BeginTransaction()) 45 | { 46 | appContext.Products.AddRange(Products()); 47 | await appContext.SaveChangesAsync(); 48 | transaction.Commit(); 49 | } 50 | } 51 | 52 | } 53 | catch (Exception ex) 54 | { 55 | if (retryForAvailability < 10) 56 | { 57 | retryForAvailability++; 58 | var log = loggerFactory.CreateLogger(); 59 | log.LogError(ex.Message); 60 | await SeedAsync(serviceProvider, loggerFactory, retryForAvailability); 61 | } 62 | } 63 | } 64 | 65 | /************* Prerequisite for Start Application ********/ 66 | 67 | static IEnumerable Roles() 68 | { 69 | return new List 70 | { 71 | new Role {Code="ADMIN", Name = "Admin", NormalizedName="ADMIN", IsActive = true, EntryDate= DateTime.Now }, 72 | new Role {Code="USER", Name = "User", NormalizedName= "USER", IsActive = true, EntryDate= DateTime.Now }, 73 | }; 74 | } 75 | 76 | static IEnumerable Products() 77 | { 78 | var faker = new Faker() 79 | .RuleFor(c => c.Code, f => f.Commerce.Product()) 80 | .RuleFor(c => c.Name, f => f.Commerce.ProductName()) 81 | .RuleFor(c => c.Description, f => f.Commerce.ProductDescription()) 82 | .RuleFor(c => c.Price, f => Convert.ToDouble(f.Commerce.Price(1, 1000, 0))) 83 | .RuleFor(c => c.Quantity, f => f.Commerce.Random.Number(100)) 84 | .RuleFor(c => c.IsActive, f => f.Random.Bool()) 85 | .RuleFor(c => c.EntryDate, DateTime.Now); 86 | 87 | return faker.Generate(100); 88 | 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Project.Infrastructure/Migrations/20240127163649_Initial.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("20240127163649_Initial")] 16 | partial class Initial 17 | { 18 | /// 19 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 | { 21 | #pragma warning disable 612, 618 22 | modelBuilder 23 | .HasAnnotation("ProductVersion", "7.0.9") 24 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 25 | 26 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 27 | 28 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 29 | { 30 | b.Property("Id") 31 | .ValueGeneratedOnAdd() 32 | .HasColumnType("int"); 33 | 34 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 35 | 36 | b.Property("ClaimType") 37 | .HasColumnType("nvarchar(max)"); 38 | 39 | b.Property("ClaimValue") 40 | .HasColumnType("nvarchar(max)"); 41 | 42 | b.Property("RoleId") 43 | .HasColumnType("int"); 44 | 45 | b.HasKey("Id"); 46 | 47 | b.HasIndex("RoleId"); 48 | 49 | b.ToTable("RoleClaims", (string)null); 50 | }); 51 | 52 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", 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("UserId") 67 | .HasColumnType("int"); 68 | 69 | b.HasKey("Id"); 70 | 71 | b.HasIndex("UserId"); 72 | 73 | b.ToTable("UserClaims", (string)null); 74 | }); 75 | 76 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 77 | { 78 | b.Property("LoginProvider") 79 | .HasColumnType("nvarchar(450)"); 80 | 81 | b.Property("ProviderKey") 82 | .HasColumnType("nvarchar(450)"); 83 | 84 | b.Property("ProviderDisplayName") 85 | .HasColumnType("nvarchar(max)"); 86 | 87 | b.Property("UserId") 88 | .HasColumnType("int"); 89 | 90 | b.HasKey("LoginProvider", "ProviderKey"); 91 | 92 | b.HasIndex("UserId"); 93 | 94 | b.ToTable("UserLogins", (string)null); 95 | }); 96 | 97 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 98 | { 99 | b.Property("UserId") 100 | .HasColumnType("int"); 101 | 102 | b.Property("RoleId") 103 | .HasColumnType("int"); 104 | 105 | b.HasKey("UserId", "RoleId"); 106 | 107 | b.HasIndex("RoleId"); 108 | 109 | b.ToTable("UserRoles", (string)null); 110 | }); 111 | 112 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 113 | { 114 | b.Property("UserId") 115 | .HasColumnType("int"); 116 | 117 | b.Property("LoginProvider") 118 | .HasColumnType("nvarchar(450)"); 119 | 120 | b.Property("Name") 121 | .HasColumnType("nvarchar(450)"); 122 | 123 | b.Property("Value") 124 | .HasColumnType("nvarchar(max)"); 125 | 126 | b.HasKey("UserId", "LoginProvider", "Name"); 127 | 128 | b.ToTable("UserTokens", (string)null); 129 | }); 130 | 131 | modelBuilder.Entity("Project.Core.Entities.General.Product", b => 132 | { 133 | b.Property("Id") 134 | .ValueGeneratedOnAdd() 135 | .HasColumnType("int"); 136 | 137 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 138 | 139 | b.Property("Code") 140 | .IsRequired() 141 | .HasMaxLength(8) 142 | .HasColumnType("nvarchar(8)"); 143 | 144 | b.Property("Description") 145 | .HasMaxLength(350) 146 | .HasColumnType("nvarchar(350)"); 147 | 148 | b.Property("EntryBy") 149 | .HasColumnType("int"); 150 | 151 | b.Property("EntryDate") 152 | .HasColumnType("datetime2"); 153 | 154 | b.Property("IsActive") 155 | .HasColumnType("bit"); 156 | 157 | b.Property("Name") 158 | .IsRequired() 159 | .HasMaxLength(100) 160 | .HasColumnType("nvarchar(100)"); 161 | 162 | b.Property("Price") 163 | .HasColumnType("float"); 164 | 165 | b.Property("Quantity") 166 | .HasColumnType("int"); 167 | 168 | b.Property("UpdatedBy") 169 | .HasColumnType("int"); 170 | 171 | b.Property("UpdatedDate") 172 | .HasColumnType("datetime2"); 173 | 174 | b.HasKey("Id"); 175 | 176 | b.ToTable("Products"); 177 | }); 178 | 179 | modelBuilder.Entity("Project.Core.Entities.General.Role", b => 180 | { 181 | b.Property("Id") 182 | .ValueGeneratedOnAdd() 183 | .HasColumnType("int"); 184 | 185 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 186 | 187 | b.Property("Code") 188 | .IsRequired() 189 | .HasMaxLength(10) 190 | .HasColumnType("nvarchar(10)"); 191 | 192 | b.Property("ConcurrencyStamp") 193 | .IsConcurrencyToken() 194 | .HasColumnType("nvarchar(max)"); 195 | 196 | b.Property("EntryBy") 197 | .HasColumnType("int"); 198 | 199 | b.Property("EntryDate") 200 | .HasColumnType("datetime2"); 201 | 202 | b.Property("IsActive") 203 | .HasColumnType("bit"); 204 | 205 | b.Property("Name") 206 | .HasMaxLength(256) 207 | .HasColumnType("nvarchar(256)"); 208 | 209 | b.Property("NormalizedName") 210 | .HasMaxLength(256) 211 | .HasColumnType("nvarchar(256)"); 212 | 213 | b.Property("UpdatedBy") 214 | .HasColumnType("int"); 215 | 216 | b.Property("UpdatedDate") 217 | .HasColumnType("datetime2"); 218 | 219 | b.HasKey("Id"); 220 | 221 | b.HasIndex("NormalizedName") 222 | .IsUnique() 223 | .HasDatabaseName("RoleNameIndex") 224 | .HasFilter("[NormalizedName] IS NOT NULL"); 225 | 226 | b.ToTable("Roles", (string)null); 227 | }); 228 | 229 | modelBuilder.Entity("Project.Core.Entities.General.User", b => 230 | { 231 | b.Property("Id") 232 | .ValueGeneratedOnAdd() 233 | .HasColumnType("int"); 234 | 235 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 236 | 237 | b.Property("AccessFailedCount") 238 | .HasColumnType("int"); 239 | 240 | b.Property("ConcurrencyStamp") 241 | .IsConcurrencyToken() 242 | .HasColumnType("nvarchar(max)"); 243 | 244 | b.Property("Email") 245 | .HasMaxLength(256) 246 | .HasColumnType("nvarchar(256)"); 247 | 248 | b.Property("EmailConfirmed") 249 | .HasColumnType("bit"); 250 | 251 | b.Property("EntryBy") 252 | .HasColumnType("int"); 253 | 254 | b.Property("EntryDate") 255 | .HasColumnType("datetime2"); 256 | 257 | b.Property("FullName") 258 | .IsRequired() 259 | .HasMaxLength(100) 260 | .HasColumnType("nvarchar(100)"); 261 | 262 | b.Property("IsActive") 263 | .HasColumnType("bit"); 264 | 265 | b.Property("LockoutEnabled") 266 | .HasColumnType("bit"); 267 | 268 | b.Property("LockoutEnd") 269 | .HasColumnType("datetimeoffset"); 270 | 271 | b.Property("NormalizedEmail") 272 | .HasMaxLength(256) 273 | .HasColumnType("nvarchar(256)"); 274 | 275 | b.Property("NormalizedUserName") 276 | .HasMaxLength(256) 277 | .HasColumnType("nvarchar(256)"); 278 | 279 | b.Property("PasswordHash") 280 | .HasColumnType("nvarchar(max)"); 281 | 282 | b.Property("PhoneNumber") 283 | .HasColumnType("nvarchar(max)"); 284 | 285 | b.Property("PhoneNumberConfirmed") 286 | .HasColumnType("bit"); 287 | 288 | b.Property("RoleId") 289 | .HasColumnType("int"); 290 | 291 | b.Property("SecurityStamp") 292 | .HasColumnType("nvarchar(max)"); 293 | 294 | b.Property("TwoFactorEnabled") 295 | .HasColumnType("bit"); 296 | 297 | b.Property("UpdatedBy") 298 | .HasColumnType("int"); 299 | 300 | b.Property("UpdatedDate") 301 | .HasColumnType("datetime2"); 302 | 303 | b.Property("UserName") 304 | .HasMaxLength(256) 305 | .HasColumnType("nvarchar(256)"); 306 | 307 | b.HasKey("Id"); 308 | 309 | b.HasIndex("NormalizedEmail") 310 | .HasDatabaseName("EmailIndex"); 311 | 312 | b.HasIndex("NormalizedUserName") 313 | .IsUnique() 314 | .HasDatabaseName("UserNameIndex") 315 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 316 | 317 | b.HasIndex("RoleId"); 318 | 319 | b.ToTable("Users", (string)null); 320 | }); 321 | 322 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 323 | { 324 | b.HasOne("Project.Core.Entities.General.Role", null) 325 | .WithMany() 326 | .HasForeignKey("RoleId") 327 | .OnDelete(DeleteBehavior.Cascade) 328 | .IsRequired(); 329 | }); 330 | 331 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 332 | { 333 | b.HasOne("Project.Core.Entities.General.User", null) 334 | .WithMany() 335 | .HasForeignKey("UserId") 336 | .OnDelete(DeleteBehavior.Cascade) 337 | .IsRequired(); 338 | }); 339 | 340 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 341 | { 342 | b.HasOne("Project.Core.Entities.General.User", null) 343 | .WithMany() 344 | .HasForeignKey("UserId") 345 | .OnDelete(DeleteBehavior.Cascade) 346 | .IsRequired(); 347 | }); 348 | 349 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 350 | { 351 | b.HasOne("Project.Core.Entities.General.Role", null) 352 | .WithMany() 353 | .HasForeignKey("RoleId") 354 | .OnDelete(DeleteBehavior.Cascade) 355 | .IsRequired(); 356 | 357 | b.HasOne("Project.Core.Entities.General.User", null) 358 | .WithMany() 359 | .HasForeignKey("UserId") 360 | .OnDelete(DeleteBehavior.Cascade) 361 | .IsRequired(); 362 | }); 363 | 364 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 365 | { 366 | b.HasOne("Project.Core.Entities.General.User", null) 367 | .WithMany() 368 | .HasForeignKey("UserId") 369 | .OnDelete(DeleteBehavior.Cascade) 370 | .IsRequired(); 371 | }); 372 | 373 | modelBuilder.Entity("Project.Core.Entities.General.User", b => 374 | { 375 | b.HasOne("Project.Core.Entities.General.Role", "Role") 376 | .WithMany() 377 | .HasForeignKey("RoleId") 378 | .OnDelete(DeleteBehavior.Cascade) 379 | .IsRequired(); 380 | 381 | b.Navigation("Role"); 382 | }); 383 | #pragma warning restore 612, 618 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /Project.Infrastructure/Migrations/20240127163649_Initial.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 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: "float", nullable: false), 23 | Quantity = table.Column(type: "int", nullable: false), 24 | Description = table.Column(type: "nvarchar(350)", maxLength: 350, nullable: true), 25 | IsActive = table.Column(type: "bit", nullable: false), 26 | EntryBy = table.Column(type: "int", nullable: true), 27 | EntryDate = table.Column(type: "datetime2", nullable: true), 28 | UpdatedBy = table.Column(type: "int", nullable: true), 29 | UpdatedDate = table.Column(type: "datetime2", nullable: true) 30 | }, 31 | constraints: table => 32 | { 33 | table.PrimaryKey("PK_Products", x => x.Id); 34 | }); 35 | 36 | migrationBuilder.CreateTable( 37 | name: "Roles", 38 | columns: table => new 39 | { 40 | Id = table.Column(type: "int", nullable: false) 41 | .Annotation("SqlServer:Identity", "1, 1"), 42 | Code = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false), 43 | IsActive = table.Column(type: "bit", nullable: false), 44 | EntryBy = table.Column(type: "int", nullable: true), 45 | EntryDate = table.Column(type: "datetime2", nullable: true), 46 | UpdatedBy = table.Column(type: "int", nullable: true), 47 | UpdatedDate = table.Column(type: "datetime2", nullable: true), 48 | Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 49 | NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 50 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) 51 | }, 52 | constraints: table => 53 | { 54 | table.PrimaryKey("PK_Roles", x => x.Id); 55 | }); 56 | 57 | migrationBuilder.CreateTable( 58 | name: "RoleClaims", 59 | columns: table => new 60 | { 61 | Id = table.Column(type: "int", nullable: false) 62 | .Annotation("SqlServer:Identity", "1, 1"), 63 | RoleId = table.Column(type: "int", nullable: false), 64 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 65 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) 66 | }, 67 | constraints: table => 68 | { 69 | table.PrimaryKey("PK_RoleClaims", x => x.Id); 70 | table.ForeignKey( 71 | name: "FK_RoleClaims_Roles_RoleId", 72 | column: x => x.RoleId, 73 | principalTable: "Roles", 74 | principalColumn: "Id", 75 | onDelete: ReferentialAction.Cascade); 76 | }); 77 | 78 | migrationBuilder.CreateTable( 79 | name: "Users", 80 | columns: table => new 81 | { 82 | Id = table.Column(type: "int", nullable: false) 83 | .Annotation("SqlServer:Identity", "1, 1"), 84 | FullName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), 85 | IsActive = table.Column(type: "bit", nullable: false), 86 | RoleId = table.Column(type: "int", nullable: false), 87 | EntryBy = table.Column(type: "int", nullable: true), 88 | EntryDate = table.Column(type: "datetime2", nullable: true), 89 | UpdatedBy = table.Column(type: "int", nullable: true), 90 | UpdatedDate = table.Column(type: "datetime2", nullable: true), 91 | UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 92 | NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 93 | Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 94 | NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 95 | EmailConfirmed = table.Column(type: "bit", nullable: false), 96 | PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), 97 | SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), 98 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), 99 | PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), 100 | PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), 101 | TwoFactorEnabled = table.Column(type: "bit", nullable: false), 102 | LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), 103 | LockoutEnabled = table.Column(type: "bit", nullable: false), 104 | AccessFailedCount = table.Column(type: "int", nullable: false) 105 | }, 106 | constraints: table => 107 | { 108 | table.PrimaryKey("PK_Users", x => x.Id); 109 | table.ForeignKey( 110 | name: "FK_Users_Roles_RoleId", 111 | column: x => x.RoleId, 112 | principalTable: "Roles", 113 | principalColumn: "Id", 114 | onDelete: ReferentialAction.Cascade); 115 | }); 116 | 117 | migrationBuilder.CreateTable( 118 | name: "UserClaims", 119 | columns: table => new 120 | { 121 | Id = table.Column(type: "int", nullable: false) 122 | .Annotation("SqlServer:Identity", "1, 1"), 123 | UserId = table.Column(type: "int", nullable: false), 124 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 125 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) 126 | }, 127 | constraints: table => 128 | { 129 | table.PrimaryKey("PK_UserClaims", x => x.Id); 130 | table.ForeignKey( 131 | name: "FK_UserClaims_Users_UserId", 132 | column: x => x.UserId, 133 | principalTable: "Users", 134 | principalColumn: "Id", 135 | onDelete: ReferentialAction.Cascade); 136 | }); 137 | 138 | migrationBuilder.CreateTable( 139 | name: "UserLogins", 140 | columns: table => new 141 | { 142 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), 143 | ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), 144 | ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), 145 | UserId = table.Column(type: "int", nullable: false) 146 | }, 147 | constraints: table => 148 | { 149 | table.PrimaryKey("PK_UserLogins", x => new { x.LoginProvider, x.ProviderKey }); 150 | table.ForeignKey( 151 | name: "FK_UserLogins_Users_UserId", 152 | column: x => x.UserId, 153 | principalTable: "Users", 154 | principalColumn: "Id", 155 | onDelete: ReferentialAction.Cascade); 156 | }); 157 | 158 | migrationBuilder.CreateTable( 159 | name: "UserRoles", 160 | columns: table => new 161 | { 162 | UserId = table.Column(type: "int", nullable: false), 163 | RoleId = table.Column(type: "int", nullable: false) 164 | }, 165 | constraints: table => 166 | { 167 | table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId }); 168 | table.ForeignKey( 169 | name: "FK_UserRoles_Roles_RoleId", 170 | column: x => x.RoleId, 171 | principalTable: "Roles", 172 | principalColumn: "Id", 173 | onDelete: ReferentialAction.NoAction); 174 | table.ForeignKey( 175 | name: "FK_UserRoles_Users_UserId", 176 | column: x => x.UserId, 177 | principalTable: "Users", 178 | principalColumn: "Id", 179 | onDelete: ReferentialAction.NoAction); 180 | }); 181 | 182 | migrationBuilder.CreateTable( 183 | name: "UserTokens", 184 | columns: table => new 185 | { 186 | UserId = table.Column(type: "int", nullable: false), 187 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), 188 | Name = table.Column(type: "nvarchar(450)", nullable: false), 189 | Value = table.Column(type: "nvarchar(max)", nullable: true) 190 | }, 191 | constraints: table => 192 | { 193 | table.PrimaryKey("PK_UserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 194 | table.ForeignKey( 195 | name: "FK_UserTokens_Users_UserId", 196 | column: x => x.UserId, 197 | principalTable: "Users", 198 | principalColumn: "Id", 199 | onDelete: ReferentialAction.Cascade); 200 | }); 201 | 202 | migrationBuilder.CreateIndex( 203 | name: "IX_RoleClaims_RoleId", 204 | table: "RoleClaims", 205 | column: "RoleId"); 206 | 207 | migrationBuilder.CreateIndex( 208 | name: "RoleNameIndex", 209 | table: "Roles", 210 | column: "NormalizedName", 211 | unique: true, 212 | filter: "[NormalizedName] IS NOT NULL"); 213 | 214 | migrationBuilder.CreateIndex( 215 | name: "IX_UserClaims_UserId", 216 | table: "UserClaims", 217 | column: "UserId"); 218 | 219 | migrationBuilder.CreateIndex( 220 | name: "IX_UserLogins_UserId", 221 | table: "UserLogins", 222 | column: "UserId"); 223 | 224 | migrationBuilder.CreateIndex( 225 | name: "IX_UserRoles_RoleId", 226 | table: "UserRoles", 227 | column: "RoleId"); 228 | 229 | migrationBuilder.CreateIndex( 230 | name: "EmailIndex", 231 | table: "Users", 232 | column: "NormalizedEmail"); 233 | 234 | migrationBuilder.CreateIndex( 235 | name: "IX_Users_RoleId", 236 | table: "Users", 237 | column: "RoleId"); 238 | 239 | migrationBuilder.CreateIndex( 240 | name: "UserNameIndex", 241 | table: "Users", 242 | column: "NormalizedUserName", 243 | unique: true, 244 | filter: "[NormalizedUserName] IS NOT NULL"); 245 | } 246 | 247 | /// 248 | protected override void Down(MigrationBuilder migrationBuilder) 249 | { 250 | migrationBuilder.DropTable( 251 | name: "Products"); 252 | 253 | migrationBuilder.DropTable( 254 | name: "RoleClaims"); 255 | 256 | migrationBuilder.DropTable( 257 | name: "UserClaims"); 258 | 259 | migrationBuilder.DropTable( 260 | name: "UserLogins"); 261 | 262 | migrationBuilder.DropTable( 263 | name: "UserRoles"); 264 | 265 | migrationBuilder.DropTable( 266 | name: "UserTokens"); 267 | 268 | migrationBuilder.DropTable( 269 | name: "Users"); 270 | 271 | migrationBuilder.DropTable( 272 | name: "Roles"); 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /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.9") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 22 | 23 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); 24 | 25 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 26 | { 27 | b.Property("Id") 28 | .ValueGeneratedOnAdd() 29 | .HasColumnType("int"); 30 | 31 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 32 | 33 | b.Property("ClaimType") 34 | .HasColumnType("nvarchar(max)"); 35 | 36 | b.Property("ClaimValue") 37 | .HasColumnType("nvarchar(max)"); 38 | 39 | b.Property("RoleId") 40 | .HasColumnType("int"); 41 | 42 | b.HasKey("Id"); 43 | 44 | b.HasIndex("RoleId"); 45 | 46 | b.ToTable("RoleClaims", (string)null); 47 | }); 48 | 49 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 50 | { 51 | b.Property("Id") 52 | .ValueGeneratedOnAdd() 53 | .HasColumnType("int"); 54 | 55 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 56 | 57 | b.Property("ClaimType") 58 | .HasColumnType("nvarchar(max)"); 59 | 60 | b.Property("ClaimValue") 61 | .HasColumnType("nvarchar(max)"); 62 | 63 | b.Property("UserId") 64 | .HasColumnType("int"); 65 | 66 | b.HasKey("Id"); 67 | 68 | b.HasIndex("UserId"); 69 | 70 | b.ToTable("UserClaims", (string)null); 71 | }); 72 | 73 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 74 | { 75 | b.Property("LoginProvider") 76 | .HasColumnType("nvarchar(450)"); 77 | 78 | b.Property("ProviderKey") 79 | .HasColumnType("nvarchar(450)"); 80 | 81 | b.Property("ProviderDisplayName") 82 | .HasColumnType("nvarchar(max)"); 83 | 84 | b.Property("UserId") 85 | .HasColumnType("int"); 86 | 87 | b.HasKey("LoginProvider", "ProviderKey"); 88 | 89 | b.HasIndex("UserId"); 90 | 91 | b.ToTable("UserLogins", (string)null); 92 | }); 93 | 94 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 95 | { 96 | b.Property("UserId") 97 | .HasColumnType("int"); 98 | 99 | b.Property("RoleId") 100 | .HasColumnType("int"); 101 | 102 | b.HasKey("UserId", "RoleId"); 103 | 104 | b.HasIndex("RoleId"); 105 | 106 | b.ToTable("UserRoles", (string)null); 107 | }); 108 | 109 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 110 | { 111 | b.Property("UserId") 112 | .HasColumnType("int"); 113 | 114 | b.Property("LoginProvider") 115 | .HasColumnType("nvarchar(450)"); 116 | 117 | b.Property("Name") 118 | .HasColumnType("nvarchar(450)"); 119 | 120 | b.Property("Value") 121 | .HasColumnType("nvarchar(max)"); 122 | 123 | b.HasKey("UserId", "LoginProvider", "Name"); 124 | 125 | b.ToTable("UserTokens", (string)null); 126 | }); 127 | 128 | modelBuilder.Entity("Project.Core.Entities.General.Product", b => 129 | { 130 | b.Property("Id") 131 | .ValueGeneratedOnAdd() 132 | .HasColumnType("int"); 133 | 134 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 135 | 136 | b.Property("Code") 137 | .IsRequired() 138 | .HasMaxLength(8) 139 | .HasColumnType("nvarchar(8)"); 140 | 141 | b.Property("Description") 142 | .HasMaxLength(350) 143 | .HasColumnType("nvarchar(350)"); 144 | 145 | b.Property("EntryBy") 146 | .HasColumnType("int"); 147 | 148 | b.Property("EntryDate") 149 | .HasColumnType("datetime2"); 150 | 151 | b.Property("IsActive") 152 | .HasColumnType("bit"); 153 | 154 | b.Property("Name") 155 | .IsRequired() 156 | .HasMaxLength(100) 157 | .HasColumnType("nvarchar(100)"); 158 | 159 | b.Property("Price") 160 | .HasColumnType("float"); 161 | 162 | b.Property("Quantity") 163 | .HasColumnType("int"); 164 | 165 | b.Property("UpdatedBy") 166 | .HasColumnType("int"); 167 | 168 | b.Property("UpdatedDate") 169 | .HasColumnType("datetime2"); 170 | 171 | b.HasKey("Id"); 172 | 173 | b.ToTable("Products"); 174 | }); 175 | 176 | modelBuilder.Entity("Project.Core.Entities.General.Role", b => 177 | { 178 | b.Property("Id") 179 | .ValueGeneratedOnAdd() 180 | .HasColumnType("int"); 181 | 182 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 183 | 184 | b.Property("Code") 185 | .IsRequired() 186 | .HasMaxLength(10) 187 | .HasColumnType("nvarchar(10)"); 188 | 189 | b.Property("ConcurrencyStamp") 190 | .IsConcurrencyToken() 191 | .HasColumnType("nvarchar(max)"); 192 | 193 | b.Property("EntryBy") 194 | .HasColumnType("int"); 195 | 196 | b.Property("EntryDate") 197 | .HasColumnType("datetime2"); 198 | 199 | b.Property("IsActive") 200 | .HasColumnType("bit"); 201 | 202 | b.Property("Name") 203 | .HasMaxLength(256) 204 | .HasColumnType("nvarchar(256)"); 205 | 206 | b.Property("NormalizedName") 207 | .HasMaxLength(256) 208 | .HasColumnType("nvarchar(256)"); 209 | 210 | b.Property("UpdatedBy") 211 | .HasColumnType("int"); 212 | 213 | b.Property("UpdatedDate") 214 | .HasColumnType("datetime2"); 215 | 216 | b.HasKey("Id"); 217 | 218 | b.HasIndex("NormalizedName") 219 | .IsUnique() 220 | .HasDatabaseName("RoleNameIndex") 221 | .HasFilter("[NormalizedName] IS NOT NULL"); 222 | 223 | b.ToTable("Roles", (string)null); 224 | }); 225 | 226 | modelBuilder.Entity("Project.Core.Entities.General.User", b => 227 | { 228 | b.Property("Id") 229 | .ValueGeneratedOnAdd() 230 | .HasColumnType("int"); 231 | 232 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); 233 | 234 | b.Property("AccessFailedCount") 235 | .HasColumnType("int"); 236 | 237 | b.Property("ConcurrencyStamp") 238 | .IsConcurrencyToken() 239 | .HasColumnType("nvarchar(max)"); 240 | 241 | b.Property("Email") 242 | .HasMaxLength(256) 243 | .HasColumnType("nvarchar(256)"); 244 | 245 | b.Property("EmailConfirmed") 246 | .HasColumnType("bit"); 247 | 248 | b.Property("EntryBy") 249 | .HasColumnType("int"); 250 | 251 | b.Property("EntryDate") 252 | .HasColumnType("datetime2"); 253 | 254 | b.Property("FullName") 255 | .IsRequired() 256 | .HasMaxLength(100) 257 | .HasColumnType("nvarchar(100)"); 258 | 259 | b.Property("IsActive") 260 | .HasColumnType("bit"); 261 | 262 | b.Property("LockoutEnabled") 263 | .HasColumnType("bit"); 264 | 265 | b.Property("LockoutEnd") 266 | .HasColumnType("datetimeoffset"); 267 | 268 | b.Property("NormalizedEmail") 269 | .HasMaxLength(256) 270 | .HasColumnType("nvarchar(256)"); 271 | 272 | b.Property("NormalizedUserName") 273 | .HasMaxLength(256) 274 | .HasColumnType("nvarchar(256)"); 275 | 276 | b.Property("PasswordHash") 277 | .HasColumnType("nvarchar(max)"); 278 | 279 | b.Property("PhoneNumber") 280 | .HasColumnType("nvarchar(max)"); 281 | 282 | b.Property("PhoneNumberConfirmed") 283 | .HasColumnType("bit"); 284 | 285 | b.Property("RoleId") 286 | .HasColumnType("int"); 287 | 288 | b.Property("SecurityStamp") 289 | .HasColumnType("nvarchar(max)"); 290 | 291 | b.Property("TwoFactorEnabled") 292 | .HasColumnType("bit"); 293 | 294 | b.Property("UpdatedBy") 295 | .HasColumnType("int"); 296 | 297 | b.Property("UpdatedDate") 298 | .HasColumnType("datetime2"); 299 | 300 | b.Property("UserName") 301 | .HasMaxLength(256) 302 | .HasColumnType("nvarchar(256)"); 303 | 304 | b.HasKey("Id"); 305 | 306 | b.HasIndex("NormalizedEmail") 307 | .HasDatabaseName("EmailIndex"); 308 | 309 | b.HasIndex("NormalizedUserName") 310 | .IsUnique() 311 | .HasDatabaseName("UserNameIndex") 312 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 313 | 314 | b.HasIndex("RoleId"); 315 | 316 | b.ToTable("Users", (string)null); 317 | }); 318 | 319 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 320 | { 321 | b.HasOne("Project.Core.Entities.General.Role", null) 322 | .WithMany() 323 | .HasForeignKey("RoleId") 324 | .OnDelete(DeleteBehavior.Cascade) 325 | .IsRequired(); 326 | }); 327 | 328 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 329 | { 330 | b.HasOne("Project.Core.Entities.General.User", null) 331 | .WithMany() 332 | .HasForeignKey("UserId") 333 | .OnDelete(DeleteBehavior.Cascade) 334 | .IsRequired(); 335 | }); 336 | 337 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 338 | { 339 | b.HasOne("Project.Core.Entities.General.User", null) 340 | .WithMany() 341 | .HasForeignKey("UserId") 342 | .OnDelete(DeleteBehavior.Cascade) 343 | .IsRequired(); 344 | }); 345 | 346 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 347 | { 348 | b.HasOne("Project.Core.Entities.General.Role", null) 349 | .WithMany() 350 | .HasForeignKey("RoleId") 351 | .OnDelete(DeleteBehavior.Cascade) 352 | .IsRequired(); 353 | 354 | b.HasOne("Project.Core.Entities.General.User", null) 355 | .WithMany() 356 | .HasForeignKey("UserId") 357 | .OnDelete(DeleteBehavior.Cascade) 358 | .IsRequired(); 359 | }); 360 | 361 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 362 | { 363 | b.HasOne("Project.Core.Entities.General.User", null) 364 | .WithMany() 365 | .HasForeignKey("UserId") 366 | .OnDelete(DeleteBehavior.Cascade) 367 | .IsRequired(); 368 | }); 369 | 370 | modelBuilder.Entity("Project.Core.Entities.General.User", b => 371 | { 372 | b.HasOne("Project.Core.Entities.General.Role", "Role") 373 | .WithMany() 374 | .HasForeignKey("RoleId") 375 | .OnDelete(DeleteBehavior.Cascade) 376 | .IsRequired(); 377 | 378 | b.Navigation("Role"); 379 | }); 380 | #pragma warning restore 612, 618 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /Project.Infrastructure/Project.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/AuthRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IRepositories; 4 | using Microsoft.AspNetCore.Identity; 5 | 6 | namespace Project.Infrastructure.Repositories 7 | { 8 | public class AuthRepository : IAuthRepository 9 | { 10 | private readonly UserManager _userManager; 11 | private readonly SignInManager _signInManager; 12 | 13 | public AuthRepository( 14 | UserManager userManager, 15 | SignInManager signInManager) 16 | { 17 | _userManager = userManager; 18 | _signInManager = signInManager; 19 | } 20 | 21 | 22 | public async Task> Login(string userName, string password) 23 | { 24 | var user = await _userManager.FindByNameAsync(userName); 25 | if (user == null || !user.IsActive) 26 | { 27 | return new ResponseViewModel 28 | { 29 | Success = false, 30 | }; 31 | } 32 | 33 | var result = await _signInManager.PasswordSignInAsync(user, password, isPersistent: false, lockoutOnFailure: false); 34 | 35 | if (result.Succeeded) 36 | { 37 | return new ResponseViewModel 38 | { 39 | Success = true, 40 | Data = new UserViewModel { Id = user.Id, UserName = user.UserName }, 41 | }; 42 | } 43 | else 44 | { 45 | return new ResponseViewModel 46 | { 47 | Success = false 48 | }; 49 | } 50 | 51 | } 52 | 53 | public async Task Logout() 54 | { 55 | await _signInManager.SignOutAsync(); 56 | } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/BaseRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Exceptions; 3 | using Project.Core.Interfaces.IRepositories; 4 | using Project.Infrastructure.Data; 5 | using Microsoft.EntityFrameworkCore; 6 | using System.Linq.Expressions; 7 | using Project.Core.Common; 8 | using Microsoft.EntityFrameworkCore.Internal; 9 | 10 | namespace Project.Infrastructure.Repositories 11 | { 12 | //Unit of Work Pattern 13 | public class BaseRepository : IBaseRepository where T : class 14 | { 15 | protected readonly ApplicationDbContext _dbContext; 16 | protected DbSet DbSet => _dbContext.Set(); 17 | 18 | public BaseRepository(ApplicationDbContext dbContext) 19 | { 20 | _dbContext = dbContext; 21 | } 22 | 23 | public async Task> GetAll(CancellationToken cancellationToken = default) 24 | { 25 | var data = await _dbContext.Set() 26 | .AsNoTracking() 27 | .ToListAsync(cancellationToken); 28 | 29 | return data; 30 | } 31 | 32 | public async Task> GetAll(List>> includeExpressions, CancellationToken cancellationToken = default) 33 | { 34 | var query = _dbContext.Set().AsQueryable(); 35 | 36 | if (includeExpressions != null) 37 | { 38 | query = includeExpressions.Aggregate(query, (current, includeExpression) => current.Include(includeExpression)); 39 | } 40 | 41 | var entities = await query.AsNoTracking().ToListAsync(cancellationToken); 42 | return entities; 43 | } 44 | 45 | public virtual async Task> GetPaginatedData(int pageNumber, int pageSize, CancellationToken cancellationToken = default) 46 | { 47 | var query = _dbContext.Set() 48 | .Skip((pageNumber - 1) * pageSize) 49 | .Take(pageSize) 50 | .AsNoTracking(); 51 | 52 | var data = await query.ToListAsync(cancellationToken); 53 | var totalCount = await _dbContext.Set().CountAsync(cancellationToken); 54 | 55 | return new PaginatedDataViewModel(data, totalCount); 56 | } 57 | 58 | public async Task> GetPaginatedData(int pageNumber, int pageSize, List filters, CancellationToken cancellationToken = default) 59 | { 60 | var query = _dbContext.Set().AsNoTracking(); 61 | 62 | // Apply search criteria if provided 63 | if (filters != null && filters.Any()) 64 | { 65 | var expressionTree = ExpressionBuilder.ConstructAndExpressionTree(filters); 66 | query = query.Where(expressionTree); 67 | } 68 | 69 | // Pagination 70 | var data = await query 71 | .Skip((pageNumber - 1) * pageSize) 72 | .Take(pageSize) 73 | .ToListAsync(cancellationToken); 74 | 75 | var totalCount = await query.CountAsync(cancellationToken); 76 | 77 | return new PaginatedDataViewModel(data, totalCount); 78 | } 79 | 80 | public virtual async Task> GetPaginatedData(List>> includeExpressions, int pageNumber, int pageSize, CancellationToken cancellationToken = default) 81 | { 82 | var query = _dbContext.Set() 83 | .Skip((pageNumber - 1) * pageSize) 84 | .Take(pageSize) 85 | .AsQueryable(); 86 | 87 | if (includeExpressions != null) 88 | { 89 | query = includeExpressions.Aggregate(query, (current, includeExpression) => current.Include(includeExpression)); 90 | } 91 | 92 | var data = await query.AsNoTracking().ToListAsync(cancellationToken); 93 | var totalCount = await _dbContext.Set().CountAsync(cancellationToken); 94 | 95 | return new PaginatedDataViewModel(data, totalCount); 96 | } 97 | 98 | public async Task> GetPaginatedData(int pageNumber, int pageSize, List filters, string sortBy, string sortOrder, CancellationToken cancellationToken = default) 99 | { 100 | var query = _dbContext.Set().AsNoTracking(); 101 | 102 | // Apply search criteria if provided 103 | if (filters != null && filters.Any()) 104 | { 105 | var expressionTree = ExpressionBuilder.ConstructAndExpressionTree(filters); 106 | query = query.Where(expressionTree); 107 | } 108 | 109 | // Add sorting 110 | if (!string.IsNullOrEmpty(sortBy)) 111 | { 112 | var orderByExpression = GetOrderByExpression(sortBy); 113 | query = sortOrder?.ToLower() == "desc" ? query.OrderByDescending(orderByExpression) : query.OrderBy(orderByExpression); 114 | } 115 | 116 | // Pagination 117 | var data = await query 118 | .Skip((pageNumber - 1) * pageSize) 119 | .Take(pageSize) 120 | .ToListAsync(cancellationToken); 121 | 122 | var totalCount = await query.CountAsync(cancellationToken); 123 | 124 | return new PaginatedDataViewModel(data, totalCount); 125 | } 126 | 127 | private Expression> GetOrderByExpression(string propertyName) 128 | { 129 | var parameter = Expression.Parameter(typeof(T), "x"); 130 | var property = Expression.Property(parameter, propertyName); 131 | var conversion = Expression.Convert(property, typeof(object)); 132 | 133 | return Expression.Lambda>(conversion, parameter); 134 | } 135 | 136 | public virtual async Task GetById(Tid id, CancellationToken cancellationToken = default) 137 | { 138 | var data = await _dbContext.Set().FindAsync(id, cancellationToken); 139 | if (data == null) 140 | throw new NotFoundException("No data found"); 141 | return data; 142 | } 143 | 144 | public virtual async Task GetById(List>> includeExpressions, Tid id, CancellationToken cancellationToken = default) 145 | { 146 | var query = _dbContext.Set().AsQueryable(); 147 | 148 | if (includeExpressions != null) 149 | { 150 | query = includeExpressions.Aggregate(query, (current, include) => current.Include(include)); 151 | } 152 | 153 | var data = await query.SingleOrDefaultAsync(x => EF.Property(x, "Id").Equals(id), cancellationToken); 154 | 155 | if (data == null) 156 | { 157 | throw new NotFoundException("No data found"); 158 | } 159 | 160 | return data; 161 | } 162 | 163 | public async Task IsExists(string key, Tvalue value, CancellationToken cancellationToken = default) 164 | { 165 | var parameter = Expression.Parameter(typeof(T), "x"); 166 | var property = Expression.Property(parameter, key); 167 | var constant = Expression.Constant(value); 168 | var equality = Expression.Equal(property, constant); 169 | var lambda = Expression.Lambda>(equality, parameter); 170 | 171 | return await _dbContext.Set().AnyAsync(lambda, cancellationToken); 172 | } 173 | 174 | //Before update existence check 175 | public async Task IsExistsForUpdate(Tid id, string key, string value, CancellationToken cancellationToken = default) 176 | { 177 | var parameter = Expression.Parameter(typeof(T), "x"); 178 | var property = Expression.Property(parameter, key); 179 | var constant = Expression.Constant(value); 180 | var equality = Expression.Equal(property, constant); 181 | 182 | var idProperty = Expression.Property(parameter, "Id"); 183 | var idEquality = Expression.NotEqual(idProperty, Expression.Constant(id)); 184 | 185 | var combinedExpression = Expression.AndAlso(equality, idEquality); 186 | var lambda = Expression.Lambda>(combinedExpression, parameter); 187 | 188 | return await _dbContext.Set().AnyAsync(lambda, cancellationToken); 189 | } 190 | 191 | 192 | public async Task Create(T model, CancellationToken cancellationToken = default) 193 | { 194 | await _dbContext.Set().AddAsync(model, cancellationToken); 195 | await _dbContext.SaveChangesAsync(cancellationToken); 196 | return model; 197 | } 198 | 199 | public async Task CreateRange(List model, CancellationToken cancellationToken = default) 200 | { 201 | await _dbContext.Set().AddRangeAsync(model, cancellationToken); 202 | await _dbContext.SaveChangesAsync(cancellationToken); 203 | } 204 | 205 | public async Task Update(T model, CancellationToken cancellationToken = default) 206 | { 207 | _dbContext.Set().Update(model); 208 | await _dbContext.SaveChangesAsync(cancellationToken); 209 | } 210 | 211 | public async Task Delete(T model, CancellationToken cancellationToken = default) 212 | { 213 | _dbContext.Set().Remove(model); 214 | await _dbContext.SaveChangesAsync(cancellationToken); 215 | } 216 | 217 | public async Task SaveChangeAsync(CancellationToken cancellationToken = default) 218 | { 219 | await _dbContext.SaveChangesAsync(cancellationToken); 220 | } 221 | 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/ProductRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using Project.Core.Interfaces.IRepositories; 3 | using Project.Infrastructure.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Project.Infrastructure.Repositories 7 | { 8 | public class ProductRepository : BaseRepository, IProductRepository 9 | { 10 | public ProductRepository(ApplicationDbContext dbContext) : base(dbContext) 11 | { 12 | } 13 | 14 | public async Task PriceCheck(int productId, CancellationToken cancellationToken = default) 15 | { 16 | var price = await _dbContext.Products 17 | .Where(x => x.Id == productId) 18 | .Select(x => x.Price) 19 | .FirstOrDefaultAsync(cancellationToken); 20 | return price; 21 | } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/RoleRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using Project.Core.Interfaces.IRepositories; 3 | using Project.Infrastructure.Data; 4 | 5 | namespace Project.Infrastructure.Repositories 6 | { 7 | public class RoleRepository : BaseRepository, IRoleRepository 8 | { 9 | public RoleRepository(ApplicationDbContext dbContext) : base(dbContext) 10 | { 11 | } 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Project.Infrastructure/Repositories/UserRepository.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IRepositories; 4 | using Project.Core.Interfaces.IServices; 5 | using Project.Infrastructure.Data; 6 | using Microsoft.AspNetCore.Identity; 7 | 8 | namespace Project.Infrastructure.Repositories 9 | { 10 | public class UserRepository : BaseRepository, IUserRepository 11 | { 12 | private readonly UserManager _userManager; 13 | private readonly RoleManager _roleManager; 14 | private readonly IUserContext _userContext; 15 | 16 | public UserRepository( 17 | ApplicationDbContext dbContext, 18 | UserManager userManager, 19 | RoleManager roleManager, 20 | IUserContext userContext 21 | ) : base(dbContext) 22 | { 23 | _userManager = userManager; 24 | _roleManager = roleManager; 25 | _userContext = userContext; 26 | } 27 | 28 | public async Task Create(UserCreateViewModel model) 29 | { 30 | // Check if the role exists by Id, if not, return an error 31 | var role = await _roleManager.FindByIdAsync(model.RoleId.ToString()); 32 | if (role == null) 33 | { 34 | return IdentityResult.Failed(new IdentityError { Code = "RoleNotFound", Description = $"Role with Id {model.RoleId} not found." }); 35 | } 36 | 37 | if (!role.IsActive) 38 | { 39 | return IdentityResult.Failed(new IdentityError { Code = "RoleInactive", Description = $"Inactive Role" }); 40 | } 41 | 42 | var user = new User 43 | { 44 | FullName = model.FullName, 45 | UserName = model.UserName, 46 | Email = model.Email, 47 | IsActive = true, 48 | RoleId = model.RoleId, 49 | EntryDate = DateTime.Now, 50 | EntryBy = Convert.ToInt32(_userContext.UserId) 51 | }; 52 | var result = await _userManager.CreateAsync(user, model.Password); 53 | 54 | // If user creation is successful, assign the role to the user 55 | if (result.Succeeded) 56 | { 57 | await _userManager.AddToRoleAsync(user, role.Name); 58 | } 59 | 60 | return result; 61 | } 62 | 63 | public async Task Update(UserUpdateViewModel model) 64 | { 65 | var user = await _userManager.FindByIdAsync(model.Id.ToString()); 66 | 67 | if (user == null) 68 | { 69 | return IdentityResult.Failed(new IdentityError { Description = "User not found." }); 70 | } 71 | 72 | // Check if the role exists by Id, if not, return an error 73 | var role = await _roleManager.FindByIdAsync(model.RoleId.ToString()); 74 | if (role == null) 75 | { 76 | return IdentityResult.Failed(new IdentityError { Code = "RoleNotFound", Description = $"Role with Id {model.RoleId} not found." }); 77 | } 78 | 79 | if (!role.IsActive) 80 | { 81 | return IdentityResult.Failed(new IdentityError { Code = "RoleInactive", Description = $"Inactive Role" }); 82 | } 83 | 84 | // Update the user properties 85 | user.FullName = model.FullName; 86 | user.UserName = model.UserName; 87 | user.Email = model.Email; 88 | user.RoleId = model.RoleId; 89 | user.UpdatedDate = DateTime.Now; 90 | user.UpdatedBy = Convert.ToInt32(_userContext.UserId); 91 | 92 | var result = await _userManager.UpdateAsync(user); 93 | 94 | // If user update is successful, assign the role to the user 95 | if (result.Succeeded) 96 | { 97 | // Remove existing roles 98 | var userRoles = await _userManager.GetRolesAsync(user); 99 | await _userManager.RemoveFromRolesAsync(user, userRoles); 100 | 101 | // Add the new role 102 | await _userManager.AddToRoleAsync(user, role.Name); 103 | } 104 | 105 | return result; 106 | } 107 | 108 | public async Task ResetPassword(ResetPasswordViewModel model) 109 | { 110 | var user = await _userManager.FindByNameAsync(model.UserName); 111 | 112 | if (user == null) 113 | { 114 | return IdentityResult.Failed(new IdentityError { Description = "User not found." }); 115 | } 116 | 117 | var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user); 118 | var result = await _userManager.ResetPasswordAsync(user, resetToken, model.NewPassword); 119 | 120 | return result; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Project.UnitTest/API/ProductControllerTests.cs: -------------------------------------------------------------------------------- 1 | using Project.API.Controllers.V1; 2 | using Project.Core.Entities.Business; 3 | using Project.Core.Interfaces.IServices; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Logging; 6 | using Moq; 7 | 8 | namespace Project.UnitTest.API 9 | { 10 | public class ProductControllerTests 11 | { 12 | private Mock _productServiceMock; 13 | private Mock> _loggerMock; 14 | private ProductController _productController; 15 | 16 | [SetUp] 17 | public void Setup() 18 | { 19 | _productServiceMock = new Mock(); 20 | _loggerMock = new Mock>(); 21 | _productController = new ProductController(_loggerMock.Object, _productServiceMock.Object); 22 | } 23 | 24 | [Test] 25 | public async Task Get_ReturnsViewWithListOfProducts() 26 | { 27 | // Arrange 28 | var products = new List 29 | { 30 | new ProductViewModel { Id = 1, Code = "P001", Name = "Product A", Price = 9.99f, IsActive = true }, 31 | new ProductViewModel { Id = 2, Code = "P002", Name = "Product B", Price = 19.99f, IsActive = true } 32 | }; 33 | 34 | _productServiceMock.Setup(service => service.GetAll(It.IsAny())) 35 | .ReturnsAsync(products); 36 | 37 | // Act 38 | var result = await _productController.Get(It.IsAny()); 39 | 40 | // Assert 41 | Assert.IsInstanceOf(result); 42 | var okObjectResult = (OkObjectResult)result; 43 | Assert.NotNull(okObjectResult); 44 | 45 | var model = (IEnumerable)okObjectResult.Value; 46 | Assert.NotNull(model); 47 | Assert.That(model.Count(), Is.EqualTo(products.Count)); 48 | 49 | } 50 | 51 | // Add more test methods for other controller actions, such as Create, Update, Delete, etc. 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Project.UnitTest/Core/ProductServiceTests.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.Business; 2 | using Project.Core.Entities.General; 3 | using Project.Core.Interfaces.IMapper; 4 | using Project.Core.Interfaces.IRepositories; 5 | using Project.Core.Interfaces.IServices; 6 | using Project.Core.Services; 7 | using Moq; 8 | 9 | namespace Project.UnitTest 10 | { 11 | public class ProductServiceTests 12 | { 13 | private Mock> _productViewModelMapperMock; 14 | private Mock> _productCreateMapperMock; 15 | private Mock> _productUpdateMapperMock; 16 | private Mock _productRepositoryMock; 17 | private Mock _userContextMock; 18 | 19 | [SetUp] 20 | public void Setup() 21 | { 22 | _productViewModelMapperMock = new Mock>(); 23 | _productCreateMapperMock = new Mock>(); 24 | _productUpdateMapperMock = new Mock>(); 25 | _productRepositoryMock = new Mock(); 26 | _userContextMock = new Mock(); 27 | } 28 | 29 | [Test] 30 | public async Task CreateProductAsync_ValidProduct_ReturnsCreatedProductViewModel() 31 | { 32 | // Arrange 33 | var productService = new ProductService( 34 | _productViewModelMapperMock.Object, 35 | _productCreateMapperMock.Object, 36 | _productUpdateMapperMock.Object, 37 | _productRepositoryMock.Object, 38 | _userContextMock.Object); 39 | 40 | var newProductCreateViewModel = new ProductCreateViewModel 41 | { 42 | Code = "P001", 43 | Name = "Sample Product", 44 | Price = 9.99f, 45 | Description = "Sample description", 46 | IsActive = true 47 | }; 48 | 49 | var newProductViewModel = new ProductViewModel 50 | { 51 | Code = "P001", 52 | Name = "Sample Product", 53 | Price = 9.99f, 54 | Description = "Sample description", 55 | IsActive = true 56 | }; 57 | 58 | var createdProduct = new Product 59 | { 60 | Code = "P001", 61 | Name = "Sample Product", 62 | Price = 9.99f, 63 | Description = "Sample description", 64 | IsActive = true 65 | }; 66 | 67 | _productCreateMapperMock.Setup(mapper => mapper.MapModel(newProductCreateViewModel)) 68 | .Returns(createdProduct); 69 | 70 | _productRepositoryMock.Setup(repo => repo.Create(createdProduct, It.IsAny())) 71 | .ReturnsAsync(createdProduct); 72 | 73 | _productViewModelMapperMock.Setup(mapper => mapper.MapModel(createdProduct)) 74 | .Returns(newProductViewModel); 75 | 76 | // Act 77 | var result = await productService.Create(newProductCreateViewModel, It.IsAny()); 78 | 79 | // Assert 80 | Assert.NotNull(result); 81 | Assert.That(result.Code, Is.EqualTo(newProductViewModel.Code)); 82 | // Additional assertions for other properties 83 | } 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /Project.UnitTest/Infrastructure/ProductRepositoryTests.cs: -------------------------------------------------------------------------------- 1 | using Project.Core.Entities.General; 2 | using Project.Infrastructure.Data; 3 | using Project.Infrastructure.Repositories; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.ChangeTracking; 6 | using Moq; 7 | 8 | namespace Project.UnitTest.Infrastructure 9 | { 10 | public class ProductRepositoryTests 11 | { 12 | private Mock _dbContextMock; 13 | private ProductRepository _productRepository; 14 | 15 | [SetUp] 16 | public void Setup() 17 | { 18 | _dbContextMock = new Mock(new DbContextOptions()); 19 | _productRepository = new ProductRepository(_dbContextMock.Object); 20 | } 21 | 22 | [Test] 23 | public async Task AddAsync_ValidProduct_ReturnsAddedProduct() 24 | { 25 | 26 | // Arrange 27 | var newProduct = new Product 28 | { 29 | Code = "P001", 30 | Name = "Sample Product", 31 | Price = 9.99f, 32 | IsActive = true 33 | }; 34 | 35 | var productDbSetMock = new Mock>(); 36 | 37 | _dbContextMock.Setup(db => db.Set()) 38 | .Returns(productDbSetMock.Object); 39 | 40 | productDbSetMock.Setup(dbSet => dbSet.AddAsync(newProduct, default)) 41 | .ReturnsAsync((EntityEntry)null); 42 | 43 | // Act 44 | var result = await _productRepository.Create(newProduct, It.IsAny()); 45 | 46 | 47 | // Assert 48 | Assert.NotNull(result); 49 | Assert.That(result, Is.EqualTo(newProduct)); 50 | } 51 | 52 | // Add more test methods for other repository operations, such as GetByIdAsync, UpdateAsync, DeleteAsync, etc. 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Project.UnitTest/Project.UnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Project.UnitTest/Usings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET Core Web API: Secure, Scalable, and Elegant 2 | Explore a meticulously crafted ASP.NET Core Web API, featuring Security Identity, JWT, Unit Testing, and API Versioning. This repository embodies best coding practices, delivering a clean, efficient, and scalable solution. Feel free to ask any questions or share your thoughts. 3 | 4 | #### Reference 5 | You can visit my blog post- [Web API Project – ASP.NET Core](https://binarybytez.com/asp-net-core-web-api-project/) 6 | 7 | ## Project Structure 8 | 9 | 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. 10 | 11 | ``` 12 | ├── src 13 | │ ├── Core # Contains the core business logic and domain models, view models, etc. 14 | │ ├── Infrastructure # Contains infrastructure concerns such as data access, external services, etc. 15 | │ └── API # Contains the API layer, including controllers, extensions, etc. 16 | ├── tests 17 | │ ├── Core.Tests # Contains unit tests for the core layer 18 | │ ├── Infrastructure.Tests # Contains unit tests for the infrastructure layer 19 | │ └── API.Tests # Contains unit tests for the API layer 20 | └── README.md # Project documentation (you are here!) 21 | ``` 22 | 23 | ## Getting Started 24 | 25 | To use this project template, follow the steps below: 26 | 27 | 1. Ensure that you have the .NET 7 SDK installed on your machine. 28 | 2. Clone or download this repository to your local machine. 29 | 3. Open the solution in your preferred IDE (e.g., Visual Studio, Visual Studio Code). 30 | 4. Build the solution to restore NuGet packages and compile the code. 31 | 5. Configure the necessary database connection settings in the `appsettings.json` file of the Infrastructure project. 32 | 6. Open the Package Manager Console, select `Project.Infrastructure` project, and run the `Update-Database` command to create the database. 33 | 7. Run the application by starting the `Project.API` project. 34 | 35 | ## Project Features 36 | 37 | This project template includes the following features: 38 | 39 | - **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. 40 | - **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. 41 | - **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. 42 | - **Unit of Work Pattern**: The unit of work pattern helps manage transactions and ensures consistency when working with multiple repositories. 43 | - **Entity Framework Core**: The project utilizes Entity Framework Core as the ORM (Object-Relational Mapping) tool for data access. 44 | - **ASP.NET Core API**: The project includes an [ASP.NET Core API project](https://binarybytez.com/clean-structured-project/) that serves as the API layer, handling HTTP requests and responses. 45 | - **JWT for Token-based Authentication**: Effortlessly manage user sessions, authentication, and authorization with this state-of-the-art token-based approach. 46 | - **API Versioning**: The project embraces API versioning to support evolutionary changes while preserving backward compatibility. 47 | - **CRUD Operations**: The project template provides a foundation for implementing complete CRUD (Create, Read, Update, Delete) operations on entities using Entity Framework Core. 48 | - **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. 49 | - **Unit Testing**: The solution includes separate test projects for unit testing the core, infrastructure, and API layers. 50 | 51 | ## Usage 52 | 53 | [The project template](https://binarybytez.com/asp-net-core-web-api-project/) provides a starting point for building RESTful APIs using ASP.NET Core. You can modify and extend the existing code to suit your specific application requirements. Here's an overview of the key components involved in building RESTful APIs: 54 | 55 | 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. 56 | 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. 57 | 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. 58 | 4. **Controllers**: The `API` project contains controllers that handle HTTP requests and responses. Update or create new controllers to expose the CRUD endpoints for your entities. Implement the appropriate HTTP methods (GET, POST, PUT, DELETE) and perform actions on the core services accordingly. 59 | 60 | 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/). 61 | 62 | ## Authors 63 | 64 | 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) 65 | 66 | Buy Me A Coffee
67 | **Thanks for your support!** 68 | 69 | ## Contributing 70 | 71 | 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. 72 | 73 | ## License 74 | 75 | This project is licensed under the [MIT License](LICENSE). 76 | 77 | -------------------------------------------------------------------------------- /WebApiProject.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33712.159 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project.Core", "Project.Core\Project.Core.csproj", "{540CC202-D687-4B54-8746-4E943F0D16C6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project.Infrastructure", "Project.Infrastructure\Project.Infrastructure.csproj", "{A9767277-DD3A-47BA-9704-E525D32F1D9D}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project.UnitTest", "Project.UnitTest\Project.UnitTest.csproj", "{9A138398-C199-404D-9E42-3095A2290A07}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project.API", "Project.API\Project.API.csproj", "{E80FE11C-4A4B-456B-A79A-9201F5A11060}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {540CC202-D687-4B54-8746-4E943F0D16C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {540CC202-D687-4B54-8746-4E943F0D16C6}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {540CC202-D687-4B54-8746-4E943F0D16C6}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {540CC202-D687-4B54-8746-4E943F0D16C6}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {A9767277-DD3A-47BA-9704-E525D32F1D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {A9767277-DD3A-47BA-9704-E525D32F1D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {A9767277-DD3A-47BA-9704-E525D32F1D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {A9767277-DD3A-47BA-9704-E525D32F1D9D}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {9A138398-C199-404D-9E42-3095A2290A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {9A138398-C199-404D-9E42-3095A2290A07}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {9A138398-C199-404D-9E42-3095A2290A07}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {9A138398-C199-404D-9E42-3095A2290A07}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {E80FE11C-4A4B-456B-A79A-9201F5A11060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {E80FE11C-4A4B-456B-A79A-9201F5A11060}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {E80FE11C-4A4B-456B-A79A-9201F5A11060}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {E80FE11C-4A4B-456B-A79A-9201F5A11060}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {554B3771-ADE1-4A3F-8808-562B64E046E0} 42 | EndGlobalSection 43 | EndGlobal 44 | --------------------------------------------------------------------------------