├── .gitattributes ├── .gitignore ├── Business ├── Abstracts │ ├── ICategoryService.cs │ └── IProductService.cs ├── Business.csproj ├── BusinessServiceRegistration.cs ├── Concretes │ ├── CategoryManager.cs │ └── ProductManager.cs ├── Dtos │ ├── Requests │ │ ├── CreateCategoryRequest.cs │ │ └── CreateProductRequest.cs │ └── Responses │ │ ├── CreatedCategoryResponse.cs │ │ ├── CreatedProductResponse.cs │ │ └── GetListProductResponse.cs ├── Messages │ └── BusinessMessages.cs ├── Profiles │ ├── CategoryMappingProfile.cs │ └── ProductMappingProfile.cs └── Rules │ ├── CategoryBusinessRules.cs │ └── ProductBusinessRules.cs ├── Core ├── Business │ └── Rules │ │ └── BaseBusinessRules.cs ├── Core.csproj ├── CrossCuttingConcerns │ └── Exceptions │ │ ├── ExceptionMiddleware.cs │ │ ├── Extensions │ │ ├── ExceptionMiddlewareExtensions.cs │ │ └── ProblemDetailsExtensions.cs │ │ ├── Handlers │ │ ├── ExceptionHandler.cs │ │ └── HttpExceptionHandler.cs │ │ ├── HttpProblemDetails │ │ └── BusinessProblemDetails.cs │ │ └── Types │ │ └── BusinessException.cs ├── DataAccess │ ├── Dynamic │ │ ├── DynamicQuery.cs │ │ ├── Filter.cs │ │ ├── IQueryableDynamicFilterExtensions.cs │ │ └── Sort.cs │ ├── Paging │ │ ├── BasePageableModel.cs │ │ ├── IPaginate.cs │ │ ├── IQueryablePaginateExtensions.cs │ │ ├── PageRequest.cs │ │ └── Paginate.cs │ └── Repositories │ │ ├── EfRepositoryBase.cs │ │ ├── IAsyncRepository.cs │ │ ├── IQuery.cs │ │ └── IRepository.cs └── Entities │ ├── Entity.cs │ └── IEntityTimestamps.cs ├── DataAccess ├── Abstracts │ ├── ICategoryDal.cs │ └── IProductDal.cs ├── Concretes │ ├── EfCategoryDal.cs │ └── EfProductDal.cs ├── Contexts │ └── NorthwindContext.cs ├── DataAccess.csproj ├── DataAccessServiceRegistration.cs └── EntityConfigurations │ ├── CategoryConfiguration.cs │ └── ProductConfiguration.cs ├── Entities ├── Concretes │ ├── Category.cs │ └── Product.cs └── Entities.csproj ├── WebApi ├── Controllers │ ├── CategoriesController.cs │ ├── ProductsController.cs │ └── WeatherForecastController.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── WeatherForecast.cs ├── WebApi.csproj ├── appsettings.Development.json └── appsettings.json └── nLayeredApp.sln /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /Business/Abstracts/ICategoryService.cs: -------------------------------------------------------------------------------- 1 | using Business.Dtos.Requests; 2 | using Business.Dtos.Responses; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Business.Abstracts; 10 | 11 | public interface ICategoryService 12 | { 13 | Task Add(CreateCategoryRequest createCategoryRequest); 14 | } 15 | -------------------------------------------------------------------------------- /Business/Abstracts/IProductService.cs: -------------------------------------------------------------------------------- 1 | using Business.Dtos.Requests; 2 | using Business.Dtos.Responses; 3 | using Core.DataAccess.Paging; 4 | using Entities.Concretes; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Business.Abstracts; 12 | 13 | public interface IProductService 14 | { 15 | Task> GetListAsync(PageRequest pageRequest); 16 | Task Add(CreateProductRequest createProductRequest); 17 | } -------------------------------------------------------------------------------- /Business/Business.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 | -------------------------------------------------------------------------------- /Business/BusinessServiceRegistration.cs: -------------------------------------------------------------------------------- 1 | using Business.Abstracts; 2 | using Business.Concretes; 3 | using Business.Rules; 4 | using DataAccess.Abstracts; 5 | using DataAccess.Concretes; 6 | using DataAccess.Contexts; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Reflection; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | 16 | namespace Business; 17 | 18 | public static class BusinessServiceRegistration 19 | { 20 | public static IServiceCollection AddBusinessServices(this IServiceCollection services) 21 | { 22 | services.AddAutoMapper(Assembly.GetExecutingAssembly()); 23 | 24 | services.AddScoped(); 25 | services.AddScoped(); 26 | 27 | services.AddScoped(); 28 | services.AddScoped(); 29 | 30 | return services; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Business/Concretes/CategoryManager.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Business.Abstracts; 3 | using Business.Dtos.Requests; 4 | using Business.Dtos.Responses; 5 | using Business.Rules; 6 | using DataAccess.Abstracts; 7 | using Entities.Concretes; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace Business.Concretes; 15 | 16 | public class CategoryManager : ICategoryService 17 | { 18 | ICategoryDal _categoryDal; 19 | IMapper _mapper; 20 | CategoryBusinessRules _categoryBusinessRules; 21 | 22 | public CategoryManager(ICategoryDal categoryDal, IMapper mapper, CategoryBusinessRules categoryBusinessRules) 23 | { 24 | _categoryDal = categoryDal; 25 | _mapper = mapper; 26 | _categoryBusinessRules = categoryBusinessRules; 27 | } 28 | 29 | public async Task Add(CreateCategoryRequest createCategoryRequest) 30 | { 31 | await _categoryBusinessRules.MaximumCategoryCountIsTen(); 32 | 33 | Category category = _mapper.Map(createCategoryRequest); 34 | 35 | var createdCategory = await _categoryDal.AddAsync(category); 36 | 37 | CreatedCategoryResponse result = _mapper.Map(createdCategory); 38 | 39 | return result; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Business/Concretes/ProductManager.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Business.Abstracts; 3 | using Business.Dtos.Requests; 4 | using Business.Dtos.Responses; 5 | using Business.Rules; 6 | using Core.DataAccess.Paging; 7 | using DataAccess.Abstracts; 8 | using Entities.Concretes; 9 | using Microsoft.EntityFrameworkCore; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | 16 | namespace Business.Concretes; 17 | 18 | public class ProductManager : IProductService 19 | { 20 | IProductDal _productDal; 21 | IMapper _mapper; 22 | ProductBusinessRules _productBusinessRules; 23 | 24 | public ProductManager(IProductDal productDal, IMapper mapper, ProductBusinessRules productBusinessRules) 25 | { 26 | _productDal = productDal; 27 | _mapper = mapper; 28 | _productBusinessRules = productBusinessRules; 29 | } 30 | 31 | public async Task Add(CreateProductRequest createProductRequest) 32 | { 33 | await _productBusinessRules.EachCategoryCanContainMax20Products(createProductRequest.CategoryId); 34 | Product product = _mapper.Map(createProductRequest); 35 | 36 | Product createdProduct = await _productDal.AddAsync(product); 37 | 38 | CreatedProductResponse createdProductResponse = _mapper.Map(createdProduct); 39 | 40 | return createdProductResponse; 41 | } 42 | 43 | public async Task> GetListAsync(PageRequest pageRequest) 44 | { 45 | var data = await _productDal.GetListAsync( 46 | include:p=>p.Include(p=>p.Category), 47 | index: pageRequest.PageIndex, 48 | size: pageRequest.PageSize 49 | 50 | ); 51 | 52 | var result = _mapper.Map>(data); 53 | return result; 54 | } 55 | } -------------------------------------------------------------------------------- /Business/Dtos/Requests/CreateCategoryRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Business.Dtos.Requests; 2 | 3 | public class CreateCategoryRequest 4 | { 5 | public string Name { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /Business/Dtos/Requests/CreateProductRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Business.Dtos.Requests; 8 | 9 | public class CreateProductRequest 10 | { 11 | public int CategoryId { get; set; } 12 | public string ProductName { get; set; } 13 | public decimal UnitPrice { get; set; } 14 | public short UnitsInStock { get; set; } 15 | public string QuantityPerUnit { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /Business/Dtos/Responses/CreatedCategoryResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Business.Dtos.Responses; 2 | 3 | public class CreatedCategoryResponse 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /Business/Dtos/Responses/CreatedProductResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Business.Dtos.Responses; 8 | 9 | public class CreatedProductResponse 10 | { 11 | public int Id { get; set; } 12 | public string ProductName { get; set; } 13 | public decimal UnitPrice { get; set; } 14 | public short UnitsInStock { get; set; } 15 | public string QuantityPerUnit { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /Business/Dtos/Responses/GetListProductResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Business.Dtos.Responses; 8 | 9 | public class GetListProductResponse 10 | { 11 | public int Id { get; set; } 12 | public int CategoryId { get; set; } 13 | public string CategoryName { get; set; } 14 | public string ProductName { get; set; } 15 | public decimal UnitPrice { get; set; } 16 | public short UnitsInStock { get; set; } 17 | public string QuantityPerUnit { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /Business/Messages/BusinessMessages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Business.Messages; 8 | 9 | public class BusinessMessages 10 | { 11 | public static string CategoryLimit = "Kategori sayısı max 10 olabilir"; 12 | public static string? CategoryProductLimit = "Bir kategoride max 20 ürün olabilir"; 13 | } 14 | -------------------------------------------------------------------------------- /Business/Profiles/CategoryMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Business.Dtos.Requests; 3 | using Business.Dtos.Responses; 4 | using Entities.Concretes; 5 | 6 | namespace Business.Profiles; 7 | 8 | public class CategoryMappingProfile : Profile 9 | { 10 | public CategoryMappingProfile() 11 | { 12 | CreateMap().ReverseMap(); 13 | CreateMap().ReverseMap(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Business/Profiles/ProductMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Business.Dtos.Requests; 3 | using Business.Dtos.Responses; 4 | using Core.DataAccess.Paging; 5 | using Entities.Concretes; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace Business.Profiles; 13 | 14 | public class ProductMappingProfile:Profile 15 | { 16 | public ProductMappingProfile() 17 | { 18 | CreateMap() 19 | .ForMember(destinationMember: p => p.CategoryName, 20 | memberOptions: opt => opt.MapFrom(p => p.Category.Name)) 21 | .ReverseMap(); 22 | 23 | CreateMap, Paginate>(); 24 | CreateMap().ReverseMap(); 25 | CreateMap().ReverseMap(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Business/Rules/CategoryBusinessRules.cs: -------------------------------------------------------------------------------- 1 | using Business.Messages; 2 | using Core.Business.Rules; 3 | using Core.CrossCuttingConcerns.Exceptions.Types; 4 | using DataAccess.Abstracts; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Business.Rules; 12 | 13 | public class CategoryBusinessRules:BaseBusinessRules 14 | { 15 | private readonly ICategoryDal _categoryDal; 16 | 17 | public CategoryBusinessRules(ICategoryDal categoryDal) 18 | { 19 | _categoryDal = categoryDal; 20 | } 21 | 22 | public async Task MaximumCategoryCountIsTen() 23 | { 24 | var result = await _categoryDal.GetListAsync(); 25 | 26 | if (result.Count >=10) 27 | { 28 | throw new BusinessException(BusinessMessages.CategoryLimit); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Business/Rules/ProductBusinessRules.cs: -------------------------------------------------------------------------------- 1 | using Business.Messages; 2 | using Core.Business.Rules; 3 | using Core.CrossCuttingConcerns.Exceptions.Types; 4 | using DataAccess.Abstracts; 5 | 6 | namespace Business.Rules; 7 | 8 | public class ProductBusinessRules : BaseBusinessRules 9 | { 10 | private readonly IProductDal _productDal; 11 | 12 | public ProductBusinessRules(IProductDal productDal) 13 | { 14 | _productDal = productDal; 15 | } 16 | 17 | public async Task EachCategoryCanContainMax20Products(int categoryId) 18 | { 19 | var result = await _productDal.GetListAsync( 20 | predicate: p => p.CategoryId == categoryId, 21 | size : 25 22 | ); 23 | 24 | if (result.Count >= 20) 25 | { 26 | throw new BusinessException(BusinessMessages.CategoryProductLimit); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Core/Business/Rules/BaseBusinessRules.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Core.Business.Rules; 8 | 9 | public class BaseBusinessRules 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /Core/Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Core/CrossCuttingConcerns/Exceptions/ExceptionMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Core.CrossCuttingConcerns.Exceptions.Handlers; 2 | using Microsoft.AspNetCore.Http; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Core.CrossCuttingConcerns.Exceptions; 10 | 11 | public class ExceptionMiddleware 12 | { 13 | private readonly RequestDelegate _next; 14 | private readonly HttpExceptionHandler _httpExceptionHandler; 15 | 16 | public ExceptionMiddleware(RequestDelegate next) 17 | { 18 | _next = next; 19 | _httpExceptionHandler = new HttpExceptionHandler(); 20 | } 21 | 22 | public async Task Invoke(HttpContext context) 23 | { 24 | try 25 | { 26 | await _next(context); 27 | } 28 | catch (Exception exception) 29 | { 30 | await HandleExceptionAsync(context.Response, exception); 31 | } 32 | 33 | } 34 | 35 | private Task HandleExceptionAsync(HttpResponse response, Exception exception) 36 | { 37 | response.ContentType = "application/json"; 38 | _httpExceptionHandler.Response = response; 39 | return _httpExceptionHandler.HandleExceptionAsync(exception); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Core/CrossCuttingConcerns/Exceptions/Extensions/ExceptionMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Core.CrossCuttingConcerns.Exceptions.Extensions; 9 | 10 | public static class ExceptionMiddlewareExtensions 11 | { 12 | public static void ConfigureCustomExceptionMiddleware(this IApplicationBuilder app) 13 | => app.UseMiddleware(); 14 | } 15 | -------------------------------------------------------------------------------- /Core/CrossCuttingConcerns/Exceptions/Extensions/ProblemDetailsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Text.Json; 3 | 4 | namespace Core.CrossCuttingConcerns.Exceptions.Extensions; 5 | 6 | public static class ProblemDetailsExtensions 7 | { 8 | public static string AsJson(this TProblemDetail details) 9 | where TProblemDetail : ProblemDetails => JsonSerializer.Serialize(details); 10 | } 11 | -------------------------------------------------------------------------------- /Core/CrossCuttingConcerns/Exceptions/Handlers/ExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using Core.CrossCuttingConcerns.Exceptions.Types; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Core.CrossCuttingConcerns.Exceptions.Handlers; 10 | 11 | public abstract class ExceptionHandler 12 | { 13 | public Task HandleExceptionAsync(Exception exception) => 14 | exception switch 15 | { 16 | BusinessException businessException => HandleException(businessException), 17 | //ValidationException validationException => HandleException(validationException), 18 | //_ => HandleException(exception) 19 | }; 20 | 21 | protected abstract Task HandleException(BusinessException businessException); 22 | //protected abstract Task HandleException(ValidationException validationException); 23 | //protected abstract Task HandleException(Exception exception); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Core/CrossCuttingConcerns/Exceptions/Handlers/HttpExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using Core.CrossCuttingConcerns.Exceptions.Extensions; 2 | using Core.CrossCuttingConcerns.Exceptions.HttpProblemDetails; 3 | using Core.CrossCuttingConcerns.Exceptions.Types; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc; 6 | using System.ComponentModel.DataAnnotations; 7 | 8 | namespace Core.CrossCuttingConcerns.Exceptions.Handlers; 9 | 10 | public class HttpExceptionHandler : ExceptionHandler 11 | { 12 | private HttpResponse? _response; 13 | public HttpResponse Response 14 | { 15 | get => _response ?? throw new ArgumentNullException(nameof(_response)); 16 | set => _response = value; 17 | } 18 | protected override Task HandleException(BusinessException businessException) 19 | { 20 | Response.StatusCode = StatusCodes.Status400BadRequest; 21 | string details = new BusinessProblemDetails(businessException.Message).AsJson(); 22 | return Response.WriteAsync(details); 23 | } 24 | 25 | //protected override Task HandleException(Exception exception) 26 | //{ 27 | // Response.StatusCode = StatusCodes.Status500InternalServerError; 28 | // string details = new InternalServerErrorProblemDetails(exception.Message).AsJson(); 29 | // return Response.WriteAsync(details); 30 | //} 31 | 32 | //protected override Task HandleException(ValidationException validationException) 33 | //{ 34 | // Response.StatusCode = StatusCodes.Status400BadRequest; 35 | // string details = new ValidationProblemDetails(validationException.Errors).AsJson(); 36 | // return Response.WriteAsync(details); 37 | //} 38 | } 39 | -------------------------------------------------------------------------------- /Core/CrossCuttingConcerns/Exceptions/HttpProblemDetails/BusinessProblemDetails.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Core.CrossCuttingConcerns.Exceptions.HttpProblemDetails; 10 | 11 | public class BusinessProblemDetails : ProblemDetails 12 | { 13 | public BusinessProblemDetails(string detail) 14 | { 15 | Title = "Rule Violation"; 16 | Detail = detail; 17 | Status = StatusCodes.Status400BadRequest; 18 | Type = "https://example.com/probs/business"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Core/CrossCuttingConcerns/Exceptions/Types/BusinessException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Core.CrossCuttingConcerns.Exceptions.Types; 8 | 9 | public class BusinessException:Exception 10 | { 11 | public BusinessException(string? message) : base(message) { } 12 | } 13 | -------------------------------------------------------------------------------- /Core/DataAccess/Dynamic/DynamicQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Core.DataAccess.Dynamic; 7 | 8 | public class DynamicQuery 9 | { 10 | public IEnumerable? Sort { get; set; } 11 | public Filter? Filter { get; set; } 12 | 13 | public DynamicQuery() { } 14 | 15 | public DynamicQuery(IEnumerable? sort, Filter? filter) 16 | { 17 | Sort = sort; 18 | Filter = filter; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Core/DataAccess/Dynamic/Filter.cs: -------------------------------------------------------------------------------- 1 | namespace Core.DataAccess.Dynamic; 2 | 3 | public class Filter 4 | { 5 | public string Field { get; set; } 6 | public string Operator { get; set; } 7 | public string? Value { get; set; } 8 | public string? Logic { get; set; } 9 | public IEnumerable? Filters { get; set; } 10 | 11 | public Filter() 12 | { 13 | Field = string.Empty; 14 | Operator = string.Empty; 15 | } 16 | 17 | public Filter(string field, string @operator) 18 | { 19 | Field = field; 20 | Operator = @operator; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Core/DataAccess/Dynamic/IQueryableDynamicFilterExtensions.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Linq.Dynamic.Core; 3 | using System.Text; 4 | 5 | namespace Core.DataAccess.Dynamic; 6 | 7 | public static class IQueryableDynamicFilterExtensions 8 | { 9 | private static readonly string[] _orders = { "asc", "desc" }; 10 | private static readonly string[] _logics = { "and", "or" }; 11 | 12 | private static readonly IDictionary _operators = new Dictionary 13 | { 14 | { "eq", "=" }, 15 | { "neq", "!=" }, 16 | { "lt", "<" }, 17 | { "lte", "<=" }, 18 | { "gt", ">" }, 19 | { "gte", ">=" }, 20 | { "isnull", "== null" }, 21 | { "isnotnull", "!= null" }, 22 | { "startswith", "StartsWith" }, 23 | { "endswith", "EndsWith" }, 24 | { "contains", "Contains" }, 25 | { "doesnotcontain", "Contains" } 26 | }; 27 | 28 | public static IQueryable ToDynamic(this IQueryable query, DynamicQuery dynamicQuery) 29 | { 30 | if (dynamicQuery.Filter is not null) 31 | query = Filter(query, dynamicQuery.Filter); 32 | if (dynamicQuery.Sort is not null && dynamicQuery.Sort.Any()) 33 | query = Sort(query, dynamicQuery.Sort); 34 | return query; 35 | } 36 | 37 | private static IQueryable Filter(IQueryable queryable, Filter filter) 38 | { 39 | IList filters = GetAllFilters(filter); 40 | string?[] values = filters.Select(f => f.Value).ToArray(); 41 | string where = Transform(filter, filters); 42 | if (!string.IsNullOrEmpty(where) && values != null) 43 | queryable = queryable.Where(where, values); 44 | 45 | return queryable; 46 | } 47 | 48 | private static IQueryable Sort(IQueryable queryable, IEnumerable sort) 49 | { 50 | foreach (Sort item in sort) 51 | { 52 | if (string.IsNullOrEmpty(item.Field)) 53 | throw new ArgumentException("Invalid Field"); 54 | if (string.IsNullOrEmpty(item.Dir) || !_orders.Contains(item.Dir)) 55 | throw new ArgumentException("Invalid Order Type"); 56 | } 57 | 58 | if (sort.Any()) 59 | { 60 | string ordering = string.Join(separator: ",", values: sort.Select(s => $"{s.Field} {s.Dir}")); 61 | return queryable.OrderBy(ordering); 62 | } 63 | 64 | return queryable; 65 | } 66 | 67 | public static IList GetAllFilters(Filter filter) 68 | { 69 | List filters = new(); 70 | GetFilters(filter, filters); 71 | return filters; 72 | } 73 | 74 | private static void GetFilters(Filter filter, IList filters) 75 | { 76 | filters.Add(filter); 77 | if (filter.Filters is not null && filter.Filters.Any()) 78 | foreach (Filter item in filter.Filters) 79 | GetFilters(item, filters); 80 | } 81 | 82 | public static string Transform(Filter filter, IList filters) 83 | { 84 | if (string.IsNullOrEmpty(filter.Field)) 85 | throw new ArgumentException("Invalid Field"); 86 | if (string.IsNullOrEmpty(filter.Operator) || !_operators.ContainsKey(filter.Operator)) 87 | throw new ArgumentException("Invalid Operator"); 88 | 89 | int index = filters.IndexOf(filter); 90 | string comparison = _operators[filter.Operator]; 91 | StringBuilder where = new(); 92 | 93 | if (!string.IsNullOrEmpty(filter.Value)) 94 | { 95 | if (filter.Operator == "doesnotcontain") 96 | where.Append($"(!np({filter.Field}).{comparison}(@{index.ToString()}))"); 97 | else if (comparison is "StartsWith" or "EndsWith" or "Contains") 98 | where.Append($"(np({filter.Field}).{comparison}(@{index.ToString()}))"); 99 | else 100 | where.Append($"np({filter.Field}) {comparison} @{index.ToString()}"); 101 | } 102 | else if (filter.Operator is "isnull" or "isnotnull") 103 | { 104 | where.Append($"np({filter.Field}) {comparison}"); 105 | } 106 | 107 | if (filter.Logic is not null && filter.Filters is not null && filter.Filters.Any()) 108 | { 109 | if (!_logics.Contains(filter.Logic)) 110 | throw new ArgumentException("Invalid Logic"); 111 | return $"{where} {filter.Logic} ({string.Join(separator: $" {filter.Logic} ", value: filter.Filters.Select(f => Transform(f, filters)).ToArray())})"; 112 | } 113 | 114 | return where.ToString(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Core/DataAccess/Dynamic/Sort.cs: -------------------------------------------------------------------------------- 1 | namespace Core.DataAccess.Dynamic; 2 | 3 | public class Sort 4 | { 5 | public string Field { get; set; } 6 | public string Dir { get; set; } 7 | 8 | public Sort() 9 | { 10 | Field = string.Empty; 11 | Dir = string.Empty; 12 | } 13 | 14 | public Sort(string field, string dir) 15 | { 16 | Field = field; 17 | Dir = dir; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Core/DataAccess/Paging/BasePageableModel.cs: -------------------------------------------------------------------------------- 1 | namespace Core.DataAccess.Paging; 2 | 3 | public abstract class BasePageableModel 4 | { 5 | public int Index { get; set; } 6 | public int Size { get; set; } 7 | public int Count { get; set; } 8 | public int Pages { get; set; } 9 | public bool HasPrevious { get; set; } 10 | public bool HasNext { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /Core/DataAccess/Paging/IPaginate.cs: -------------------------------------------------------------------------------- 1 | namespace Core.DataAccess.Paging; 2 | 3 | public interface IPaginate 4 | { 5 | int From { get; } 6 | int Index { get; } 7 | int Size { get; } 8 | int Count { get; } 9 | int Pages { get; } 10 | IList Items { get; } 11 | bool HasPrevious { get; } 12 | bool HasNext { get; } 13 | } 14 | -------------------------------------------------------------------------------- /Core/DataAccess/Paging/IQueryablePaginateExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Core.DataAccess.Paging; 9 | 10 | public static class IQueryablePaginateExtensions 11 | { 12 | public static async Task> ToPaginateAsync( 13 | this IQueryable source, 14 | int index, 15 | int size, 16 | int from = 0, 17 | CancellationToken cancellationToken = default 18 | ) 19 | { 20 | if (from > index) 21 | throw new ArgumentException($"From: {from.ToString()} > Index: {index.ToString()}, must from <= Index"); 22 | 23 | int count = await source.CountAsync(cancellationToken).ConfigureAwait(false); 24 | 25 | List items = await source.Skip((index - from) * size).Take(size).ToListAsync(cancellationToken).ConfigureAwait(false); 26 | 27 | Paginate list = 28 | new() 29 | { 30 | Index = index, 31 | Size = size, 32 | From = from, 33 | Count = count, 34 | Items = items, 35 | Pages = (int)Math.Ceiling(count / (double)size) 36 | }; 37 | return list; 38 | } 39 | 40 | public static IPaginate ToPaginate(this IQueryable source, int index, int size, int from = 0) 41 | { 42 | if (from > index) 43 | throw new ArgumentException($"From: {from.ToString()} > Index: {index.ToString()}, must from <= Index"); 44 | 45 | int count = source.Count(); 46 | var items = source.Skip((index - from) * size).Take(size).ToList(); 47 | 48 | Paginate list = 49 | new() 50 | { 51 | Index = index, 52 | Size = size, 53 | From = from, 54 | Count = count, 55 | Items = items, 56 | Pages = (int)Math.Ceiling(count / (double)size) 57 | }; 58 | return list; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Core/DataAccess/Paging/PageRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Core.DataAccess.Paging; 8 | 9 | public class PageRequest 10 | { 11 | public int PageIndex { get; set; } 12 | public int PageSize { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /Core/DataAccess/Paging/Paginate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Core.DataAccess.Paging; 8 | 9 | public class Paginate : IPaginate 10 | { 11 | public Paginate(IEnumerable source, int index, int size, int from) 12 | { 13 | if (from > index) 14 | throw new ArgumentException($"indexFrom: {from.ToString()} > pageIndex: {index.ToString()}, must indexFrom <= pageIndex"); 15 | 16 | Index = index; 17 | Size = size; 18 | From = from; 19 | Pages = (int)Math.Ceiling(Count / (double)Size); 20 | 21 | if (source is IQueryable queryable) 22 | { 23 | Count = queryable.Count(); 24 | Items = queryable.Skip((Index - From) * Size).Take(Size).ToList(); 25 | } 26 | else 27 | { 28 | T[] enumerable = source as T[] ?? source.ToArray(); 29 | Count = enumerable.Count(); 30 | Items = enumerable.Skip((Index - From) * Size).Take(Size).ToList(); 31 | } 32 | } 33 | 34 | public Paginate() 35 | { 36 | Items = Array.Empty(); 37 | } 38 | 39 | public int From { get; set; } 40 | public int Index { get; set; } 41 | public int Size { get; set; } 42 | public int Count { get; set; } 43 | public int Pages { get; set; } 44 | public IList Items { get; set; } 45 | public bool HasPrevious => Index - From > 0; 46 | public bool HasNext => Index - From + 1 < Pages; 47 | } 48 | 49 | public class Paginate : IPaginate 50 | { 51 | public Paginate(IEnumerable source, Func, IEnumerable> converter, int index, int size, int from) 52 | { 53 | if (from > index) 54 | throw new ArgumentException($"From: {from.ToString()} > Index: {index.ToString()}, must From <= Index"); 55 | 56 | Index = index; 57 | Size = size; 58 | From = from; 59 | Pages = (int)Math.Ceiling(Count / (double)Size); 60 | 61 | if (source is IQueryable queryable) 62 | { 63 | Count = queryable.Count(); 64 | TSource[] items = queryable.Skip((Index - From) * Size).Take(Size).ToArray(); 65 | Items = new List(converter(items)); 66 | } 67 | else 68 | { 69 | TSource[] enumerable = source as TSource[] ?? source.ToArray(); 70 | Count = enumerable.Count(); 71 | TSource[] items = enumerable.Skip((Index - From) * Size).Take(Size).ToArray(); 72 | Items = new List(converter(items)); 73 | } 74 | } 75 | 76 | public Paginate(IPaginate source, Func, IEnumerable> converter) 77 | { 78 | Index = source.Index; 79 | Size = source.Size; 80 | From = source.From; 81 | Count = source.Count; 82 | Pages = source.Pages; 83 | 84 | Items = new List(converter(source.Items)); 85 | } 86 | 87 | public int Index { get; } 88 | 89 | public int Size { get; } 90 | 91 | public int Count { get; } 92 | 93 | public int Pages { get; } 94 | 95 | public int From { get; } 96 | 97 | public IList Items { get; } 98 | 99 | public bool HasPrevious => Index - From > 0; 100 | 101 | public bool HasNext => Index - From + 1 < Pages; 102 | } 103 | 104 | public static class Paginate 105 | { 106 | public static IPaginate Empty() => new Paginate(); 107 | 108 | public static IPaginate From( 109 | IPaginate source, 110 | Func, IEnumerable> converter 111 | ) => new Paginate(source, converter); 112 | } 113 | -------------------------------------------------------------------------------- /Core/DataAccess/Repositories/EfRepositoryBase.cs: -------------------------------------------------------------------------------- 1 | using Core.DataAccess.Dynamic; 2 | using Core.DataAccess.Paging; 3 | using Core.Entities; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Query; 7 | using System.Collections; 8 | using System.Linq.Expressions; 9 | using System.Reflection; 10 | 11 | namespace Core.DataAccess.Repositories; 12 | 13 | public class EfRepositoryBase : IAsyncRepository, IRepository 14 | where TEntity : Entity 15 | where TContext : DbContext 16 | { 17 | protected readonly TContext Context; 18 | 19 | public EfRepositoryBase(TContext context) 20 | { 21 | Context = context; 22 | } 23 | 24 | public IQueryable Query() => Context.Set(); 25 | 26 | public async Task AddAsync(TEntity entity) 27 | { 28 | entity.CreatedDate = DateTime.UtcNow; 29 | await Context.AddAsync(entity); 30 | await Context.SaveChangesAsync(); 31 | return entity; 32 | } 33 | 34 | public async Task> AddRangeAsync(ICollection entities) 35 | { 36 | foreach (TEntity entity in entities) 37 | entity.CreatedDate = DateTime.UtcNow; 38 | await Context.AddRangeAsync(entities); 39 | await Context.SaveChangesAsync(); 40 | return entities; 41 | } 42 | 43 | public async Task UpdateAsync(TEntity entity) 44 | { 45 | entity.UpdatedDate = DateTime.UtcNow; 46 | Context.Update(entity); 47 | await Context.SaveChangesAsync(); 48 | return entity; 49 | } 50 | 51 | public async Task> UpdateRangeAsync(ICollection entities) 52 | { 53 | foreach (TEntity entity in entities) 54 | entity.UpdatedDate = DateTime.UtcNow; 55 | Context.UpdateRange(entities); 56 | await Context.SaveChangesAsync(); 57 | return entities; 58 | } 59 | 60 | public async Task DeleteAsync(TEntity entity, bool permanent = false) 61 | { 62 | await SetEntityAsDeletedAsync(entity, permanent); 63 | await Context.SaveChangesAsync(); 64 | return entity; 65 | } 66 | 67 | public async Task> DeleteRangeAsync(ICollection entities, bool permanent = false) 68 | { 69 | await SetEntityAsDeletedAsync(entities, permanent); 70 | await Context.SaveChangesAsync(); 71 | return entities; 72 | } 73 | 74 | public async Task> GetListAsync( 75 | Expression>? predicate = null, 76 | Func, IOrderedQueryable>? orderBy = null, 77 | Func, IIncludableQueryable>? include = null, 78 | int index = 0, 79 | int size = 10, 80 | bool withDeleted = false, 81 | bool enableTracking = true, 82 | CancellationToken cancellationToken = default 83 | ) 84 | { 85 | IQueryable queryable = Query(); 86 | if (!enableTracking) 87 | queryable = queryable.AsNoTracking(); 88 | if (include != null) 89 | queryable = include(queryable); 90 | if (withDeleted) 91 | queryable = queryable.IgnoreQueryFilters(); 92 | if (predicate != null) 93 | queryable = queryable.Where(predicate); 94 | if (orderBy != null) 95 | return await orderBy(queryable).ToPaginateAsync(index, size, from: 0, cancellationToken); 96 | return await queryable.ToPaginateAsync(index, size, from: 0, cancellationToken); 97 | } 98 | 99 | public async Task GetAsync( 100 | Expression> predicate, 101 | Func, IIncludableQueryable>? include = null, 102 | bool withDeleted = false, 103 | bool enableTracking = true, 104 | CancellationToken cancellationToken = default 105 | ) 106 | { 107 | IQueryable queryable = Query(); 108 | if (!enableTracking) 109 | queryable = queryable.AsNoTracking(); 110 | if (include != null) 111 | queryable = include(queryable); 112 | if (withDeleted) 113 | queryable = queryable.IgnoreQueryFilters(); 114 | return await queryable.FirstOrDefaultAsync(predicate, cancellationToken); 115 | } 116 | 117 | public async Task> GetListByDynamicAsync( 118 | DynamicQuery dynamic, 119 | Expression>? predicate = null, 120 | Func, IIncludableQueryable>? include = null, 121 | int index = 0, 122 | int size = 10, 123 | bool withDeleted = false, 124 | bool enableTracking = true, 125 | CancellationToken cancellationToken = default 126 | ) 127 | { 128 | IQueryable queryable = Query().ToDynamic(dynamic); 129 | if (!enableTracking) 130 | queryable = queryable.AsNoTracking(); 131 | if (include != null) 132 | queryable = include(queryable); 133 | if (withDeleted) 134 | queryable = queryable.IgnoreQueryFilters(); 135 | if (predicate != null) 136 | queryable = queryable.Where(predicate); 137 | return await queryable.ToPaginateAsync(index, size, from: 0, cancellationToken); 138 | } 139 | 140 | public async Task AnyAsync( 141 | Expression>? predicate = null, 142 | bool withDeleted = false, 143 | bool enableTracking = true, 144 | CancellationToken cancellationToken = default 145 | ) 146 | { 147 | IQueryable queryable = Query(); 148 | if (!enableTracking) 149 | queryable = queryable.AsNoTracking(); 150 | if (withDeleted) 151 | queryable = queryable.IgnoreQueryFilters(); 152 | if (predicate != null) 153 | queryable = queryable.Where(predicate); 154 | return await queryable.AnyAsync(cancellationToken); 155 | } 156 | 157 | public TEntity Add(TEntity entity) 158 | { 159 | entity.CreatedDate = DateTime.UtcNow; 160 | Context.Add(entity); 161 | Context.SaveChanges(); 162 | return entity; 163 | } 164 | 165 | public ICollection AddRange(ICollection entities) 166 | { 167 | foreach (TEntity entity in entities) 168 | entity.CreatedDate = DateTime.UtcNow; 169 | Context.AddRange(entities); 170 | Context.SaveChanges(); 171 | return entities; 172 | } 173 | 174 | public TEntity Update(TEntity entity) 175 | { 176 | entity.UpdatedDate = DateTime.UtcNow; 177 | Context.Update(entity); 178 | Context.SaveChanges(); 179 | return entity; 180 | } 181 | 182 | public ICollection UpdateRange(ICollection entities) 183 | { 184 | foreach (TEntity entity in entities) 185 | entity.UpdatedDate = DateTime.UtcNow; 186 | Context.UpdateRange(entities); 187 | Context.SaveChanges(); 188 | return entities; 189 | } 190 | 191 | public TEntity Delete(TEntity entity, bool permanent = false) 192 | { 193 | SetEntityAsDeleted(entity, permanent); 194 | Context.SaveChanges(); 195 | return entity; 196 | } 197 | 198 | public ICollection DeleteRange(ICollection entities, bool permanent = false) 199 | { 200 | SetEntityAsDeleted(entities, permanent); 201 | Context.SaveChanges(); 202 | return entities; 203 | } 204 | 205 | public TEntity? Get( 206 | Expression> predicate, 207 | Func, IIncludableQueryable>? include = null, 208 | bool withDeleted = false, 209 | bool enableTracking = true 210 | ) 211 | { 212 | IQueryable queryable = Query(); 213 | if (!enableTracking) 214 | queryable = queryable.AsNoTracking(); 215 | if (include != null) 216 | queryable = include(queryable); 217 | if (withDeleted) 218 | queryable = queryable.IgnoreQueryFilters(); 219 | return queryable.FirstOrDefault(predicate); 220 | } 221 | 222 | public IPaginate GetList( 223 | Expression>? predicate = null, 224 | Func, IOrderedQueryable>? orderBy = null, 225 | Func, IIncludableQueryable>? include = null, 226 | int index = 0, 227 | int size = 10, 228 | bool withDeleted = false, 229 | bool enableTracking = true 230 | ) 231 | { 232 | IQueryable queryable = Query(); 233 | if (!enableTracking) 234 | queryable = queryable.AsNoTracking(); 235 | if (include != null) 236 | queryable = include(queryable); 237 | if (withDeleted) 238 | queryable = queryable.IgnoreQueryFilters(); 239 | if (predicate != null) 240 | queryable = queryable.Where(predicate); 241 | if (orderBy != null) 242 | return orderBy(queryable).ToPaginate(index, size); 243 | return queryable.ToPaginate(index, size); 244 | } 245 | 246 | public IPaginate GetListByDynamic( 247 | DynamicQuery dynamic, 248 | Expression>? predicate = null, 249 | Func, IIncludableQueryable>? include = null, 250 | int index = 0, 251 | int size = 10, 252 | bool withDeleted = false, 253 | bool enableTracking = true 254 | ) 255 | { 256 | IQueryable queryable = Query().ToDynamic(dynamic); 257 | if (!enableTracking) 258 | queryable = queryable.AsNoTracking(); 259 | if (include != null) 260 | queryable = include(queryable); 261 | if (withDeleted) 262 | queryable = queryable.IgnoreQueryFilters(); 263 | if (predicate != null) 264 | queryable = queryable.Where(predicate); 265 | return queryable.ToPaginate(index, size); 266 | } 267 | 268 | public bool Any(Expression>? predicate = null, bool withDeleted = false, bool enableTracking = true) 269 | { 270 | IQueryable queryable = Query(); 271 | if (!enableTracking) 272 | queryable = queryable.AsNoTracking(); 273 | if (withDeleted) 274 | queryable = queryable.IgnoreQueryFilters(); 275 | if (predicate != null) 276 | queryable = queryable.Where(predicate); 277 | return queryable.Any(); 278 | } 279 | 280 | protected async Task SetEntityAsDeletedAsync(TEntity entity, bool permanent) 281 | { 282 | if (!permanent) 283 | { 284 | CheckHasEntityHaveOneToOneRelation(entity); 285 | await setEntityAsSoftDeletedAsync(entity); 286 | } 287 | else 288 | { 289 | Context.Remove(entity); 290 | } 291 | } 292 | 293 | protected async Task SetEntityAsDeletedAsync(IEnumerable entities, bool permanent) 294 | { 295 | foreach (TEntity entity in entities) 296 | await SetEntityAsDeletedAsync(entity, permanent); 297 | } 298 | 299 | protected void SetEntityAsDeleted(TEntity entity, bool permanent) 300 | { 301 | if (!permanent) 302 | { 303 | CheckHasEntityHaveOneToOneRelation(entity); 304 | setEntityAsSoftDeleted(entity); 305 | } 306 | else 307 | { 308 | Context.Remove(entity); 309 | } 310 | } 311 | 312 | protected void SetEntityAsDeleted(IEnumerable entities, bool permanent) 313 | { 314 | foreach (TEntity entity in entities) 315 | SetEntityAsDeleted(entity, permanent); 316 | } 317 | 318 | protected IQueryable GetRelationLoaderQuery(IQueryable query, Type navigationPropertyType) 319 | { 320 | Type queryProviderType = query.Provider.GetType(); 321 | MethodInfo createQueryMethod = 322 | queryProviderType 323 | .GetMethods() 324 | .First(m => m is { Name: nameof(query.Provider.CreateQuery), IsGenericMethod: true }) 325 | ?.MakeGenericMethod(navigationPropertyType) 326 | ?? throw new InvalidOperationException("CreateQuery method is not found in IQueryProvider."); 327 | var queryProviderQuery = 328 | (IQueryable)createQueryMethod.Invoke(query.Provider, parameters: new object[] { query.Expression })!; 329 | return queryProviderQuery.Where(x => !((IEntityTimestamps)x).DeletedDate.HasValue); 330 | } 331 | 332 | protected void CheckHasEntityHaveOneToOneRelation(TEntity entity) 333 | { 334 | bool hasEntityHaveOneToOneRelation = 335 | Context 336 | .Entry(entity) 337 | .Metadata.GetForeignKeys() 338 | .All( 339 | x => 340 | x.DependentToPrincipal?.IsCollection == true 341 | || x.PrincipalToDependent?.IsCollection == true 342 | || x.DependentToPrincipal?.ForeignKey.DeclaringEntityType.ClrType == entity.GetType() 343 | ) == false; 344 | if (hasEntityHaveOneToOneRelation) 345 | throw new InvalidOperationException( 346 | "Entity has one-to-one relationship. Soft Delete causes problems if you try to create entry again by same foreign key." 347 | ); 348 | } 349 | 350 | private async Task setEntityAsSoftDeletedAsync(IEntityTimestamps entity) 351 | { 352 | if (entity.DeletedDate.HasValue) 353 | return; 354 | entity.DeletedDate = DateTime.UtcNow; 355 | 356 | var navigations = Context 357 | .Entry(entity) 358 | .Metadata.GetNavigations() 359 | .Where(x => x is { IsOnDependent: false, ForeignKey.DeleteBehavior: DeleteBehavior.ClientCascade or DeleteBehavior.Cascade }) 360 | .ToList(); 361 | foreach (INavigation? navigation in navigations) 362 | { 363 | if (navigation.TargetEntityType.IsOwned()) 364 | continue; 365 | if (navigation.PropertyInfo == null) 366 | continue; 367 | 368 | object? navValue = navigation.PropertyInfo.GetValue(entity); 369 | if (navigation.IsCollection) 370 | { 371 | if (navValue == null) 372 | { 373 | IQueryable query = Context.Entry(entity).Collection(navigation.PropertyInfo.Name).Query(); 374 | navValue = await GetRelationLoaderQuery(query, navigationPropertyType: navigation.PropertyInfo.GetType()).ToListAsync(); 375 | if (navValue == null) 376 | continue; 377 | } 378 | 379 | foreach (IEntityTimestamps navValueItem in (IEnumerable)navValue) 380 | await setEntityAsSoftDeletedAsync(navValueItem); 381 | } 382 | else 383 | { 384 | if (navValue == null) 385 | { 386 | IQueryable query = Context.Entry(entity).Reference(navigation.PropertyInfo.Name).Query(); 387 | navValue = await GetRelationLoaderQuery(query, navigationPropertyType: navigation.PropertyInfo.GetType()) 388 | .FirstOrDefaultAsync(); 389 | if (navValue == null) 390 | continue; 391 | } 392 | 393 | await setEntityAsSoftDeletedAsync((IEntityTimestamps)navValue); 394 | } 395 | } 396 | 397 | Context.Update(entity); 398 | } 399 | 400 | private void setEntityAsSoftDeleted(IEntityTimestamps entity) 401 | { 402 | if (entity.DeletedDate.HasValue) 403 | return; 404 | entity.DeletedDate = DateTime.UtcNow; 405 | 406 | var navigations = Context 407 | .Entry(entity) 408 | .Metadata.GetNavigations() 409 | .Where(x => x is { IsOnDependent: false, ForeignKey.DeleteBehavior: DeleteBehavior.ClientCascade or DeleteBehavior.Cascade }) 410 | .ToList(); 411 | foreach (INavigation? navigation in navigations) 412 | { 413 | if (navigation.TargetEntityType.IsOwned()) 414 | continue; 415 | if (navigation.PropertyInfo == null) 416 | continue; 417 | 418 | object? navValue = navigation.PropertyInfo.GetValue(entity); 419 | if (navigation.IsCollection) 420 | { 421 | if (navValue == null) 422 | { 423 | IQueryable query = Context.Entry(entity).Collection(navigation.PropertyInfo.Name).Query(); 424 | navValue = GetRelationLoaderQuery(query, navigationPropertyType: navigation.PropertyInfo.GetType()).ToList(); 425 | if (navValue == null) 426 | continue; 427 | } 428 | 429 | foreach (IEntityTimestamps navValueItem in (IEnumerable)navValue) 430 | setEntityAsSoftDeleted(navValueItem); 431 | } 432 | else 433 | { 434 | if (navValue == null) 435 | { 436 | IQueryable query = Context.Entry(entity).Reference(navigation.PropertyInfo.Name).Query(); 437 | navValue = GetRelationLoaderQuery(query, navigationPropertyType: navigation.PropertyInfo.GetType()).FirstOrDefault(); 438 | if (navValue == null) 439 | continue; 440 | } 441 | 442 | setEntityAsSoftDeleted((IEntityTimestamps)navValue); 443 | } 444 | } 445 | 446 | Context.Update(entity); 447 | } 448 | } -------------------------------------------------------------------------------- /Core/DataAccess/Repositories/IAsyncRepository.cs: -------------------------------------------------------------------------------- 1 | using Core.DataAccess.Dynamic; 2 | using Core.DataAccess.Paging; 3 | using Core.Entities; 4 | using Microsoft.EntityFrameworkCore.Query; 5 | using System.Linq.Expressions; 6 | 7 | namespace Core.DataAccess.Repositories; 8 | 9 | public interface IAsyncRepository : IQuery 10 | where TEntity : Entity 11 | { 12 | Task GetAsync( 13 | Expression> predicate, 14 | Func, IIncludableQueryable>? include = null, 15 | bool withDeleted = false, 16 | bool enableTracking = true, 17 | CancellationToken cancellationToken = default 18 | ); 19 | 20 | Task> GetListAsync( 21 | Expression>? predicate = null, 22 | Func, IOrderedQueryable>? orderBy = null, 23 | Func, IIncludableQueryable>? include = null, 24 | int index = 0, 25 | int size = 10, 26 | bool withDeleted = false, 27 | bool enableTracking = true, 28 | CancellationToken cancellationToken = default 29 | ); 30 | 31 | Task> GetListByDynamicAsync( 32 | DynamicQuery dynamic, 33 | Expression>? predicate = null, 34 | Func, IIncludableQueryable>? include = null, 35 | int index = 0, 36 | int size = 10, 37 | bool withDeleted = false, 38 | bool enableTracking = true, 39 | CancellationToken cancellationToken = default 40 | ); 41 | 42 | Task AnyAsync( 43 | Expression>? predicate = null, 44 | bool withDeleted = false, 45 | bool enableTracking = true, 46 | CancellationToken cancellationToken = default 47 | ); 48 | 49 | Task AddAsync(TEntity entity); 50 | 51 | Task> AddRangeAsync(ICollection entity); 52 | 53 | Task UpdateAsync(TEntity entity); 54 | 55 | Task> UpdateRangeAsync(ICollection entity); 56 | 57 | Task DeleteAsync(TEntity entity, bool permanent = false); 58 | 59 | Task> DeleteRangeAsync(ICollection entity, bool permanent = false); 60 | } 61 | -------------------------------------------------------------------------------- /Core/DataAccess/Repositories/IQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Core.DataAccess.Repositories; 2 | 3 | public interface IQuery 4 | { 5 | IQueryable Query(); 6 | } -------------------------------------------------------------------------------- /Core/DataAccess/Repositories/IRepository.cs: -------------------------------------------------------------------------------- 1 | using Core.DataAccess.Dynamic; 2 | using Core.DataAccess.Paging; 3 | using Core.Entities; 4 | using Microsoft.EntityFrameworkCore.Query; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace Core.DataAccess.Repositories; 13 | 14 | public interface IRepository : IQuery 15 | where TEntity : Entity 16 | { 17 | TEntity? Get( 18 | Expression> predicate, 19 | Func, IIncludableQueryable>? include = null, 20 | bool withDeleted = false, 21 | bool enableTracking = true 22 | ); 23 | 24 | IPaginate GetList( 25 | Expression>? predicate = null, 26 | Func, IOrderedQueryable>? orderBy = null, 27 | Func, IIncludableQueryable>? include = null, 28 | int index = 0, 29 | int size = 10, 30 | bool withDeleted = false, 31 | bool enableTracking = true 32 | ); 33 | 34 | IPaginate GetListByDynamic( 35 | DynamicQuery dynamic, 36 | Expression>? predicate = null, 37 | Func, IIncludableQueryable>? include = null, 38 | int index = 0, 39 | int size = 10, 40 | bool withDeleted = false, 41 | bool enableTracking = true 42 | ); 43 | 44 | bool Any(Expression>? predicate = null, bool withDeleted = false, bool enableTracking = true); 45 | TEntity Add(TEntity entity); 46 | ICollection AddRange(ICollection entities); 47 | TEntity Update(TEntity entity); 48 | ICollection UpdateRange(ICollection entities); 49 | TEntity Delete(TEntity entity, bool permanent = false); 50 | ICollection DeleteRange(ICollection entity, bool permanent = false); 51 | } 52 | -------------------------------------------------------------------------------- /Core/Entities/Entity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Core.Entities; 8 | 9 | public class Entity : IEntityTimestamps 10 | { 11 | public TId Id { get; set; } 12 | public DateTime CreatedDate { get; set; } 13 | public DateTime? UpdatedDate { get; set; } 14 | public DateTime? DeletedDate { get; set; } 15 | 16 | public Entity() 17 | { 18 | Id = default; 19 | } 20 | 21 | public Entity(TId id) 22 | { 23 | Id = id; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Core/Entities/IEntityTimestamps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Core.Entities; 8 | 9 | public interface IEntityTimestamps 10 | { 11 | DateTime CreatedDate { get; set; } 12 | DateTime? UpdatedDate { get; set; } 13 | DateTime? DeletedDate { get; set; } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /DataAccess/Abstracts/ICategoryDal.cs: -------------------------------------------------------------------------------- 1 | using Core.DataAccess.Repositories; 2 | using Entities.Concretes; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace DataAccess.Abstracts; 10 | 11 | public interface ICategoryDal : IRepository, IAsyncRepository 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /DataAccess/Abstracts/IProductDal.cs: -------------------------------------------------------------------------------- 1 | using Core.DataAccess.Repositories; 2 | using Entities.Concretes; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace DataAccess.Abstracts; 10 | 11 | public interface IProductDal:IRepository,IAsyncRepository 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /DataAccess/Concretes/EfCategoryDal.cs: -------------------------------------------------------------------------------- 1 | using Core.DataAccess.Repositories; 2 | using DataAccess.Abstracts; 3 | using DataAccess.Contexts; 4 | using Entities.Concretes; 5 | 6 | namespace DataAccess.Concretes; 7 | 8 | public class EfCategoryDal : EfRepositoryBase, ICategoryDal 9 | { 10 | public EfCategoryDal(NorthwindContext context) : base(context) 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DataAccess/Concretes/EfProductDal.cs: -------------------------------------------------------------------------------- 1 | using Core.DataAccess.Repositories; 2 | using DataAccess.Abstracts; 3 | using DataAccess.Contexts; 4 | using Entities.Concretes; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace DataAccess.Concretes; 12 | 13 | public class EfProductDal : EfRepositoryBase, IProductDal 14 | { 15 | public EfProductDal(NorthwindContext context) : base(context) 16 | { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DataAccess/Contexts/NorthwindContext.cs: -------------------------------------------------------------------------------- 1 | using Entities.Concretes; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Configuration; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace DataAccess.Contexts; 12 | 13 | public class NorthwindContext:DbContext 14 | { 15 | protected IConfiguration Configuration { get; set; } 16 | 17 | public DbSet Products { get; set; } 18 | public DbSet Categories { get; set; } 19 | 20 | public NorthwindContext(DbContextOptions dbContextOptions, IConfiguration configuration) : base(dbContextOptions) 21 | { 22 | Configuration = configuration; 23 | //Database.EnsureCreated(); 24 | } 25 | 26 | protected override void OnModelCreating(ModelBuilder modelBuilder) 27 | { 28 | modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DataAccess/DataAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /DataAccess/DataAccessServiceRegistration.cs: -------------------------------------------------------------------------------- 1 | using DataAccess.Abstracts; 2 | using DataAccess.Concretes; 3 | using DataAccess.Contexts; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace DataAccess; 14 | 15 | public static class DataAccessServiceRegistration 16 | { 17 | public static IServiceCollection AddDataAccessServices(this IServiceCollection services, IConfiguration configuration) 18 | { 19 | //services.AddDbContext(options => options.UseInMemoryDatabase("nArchitecture")); 20 | 21 | services.AddDbContext(options => options.UseSqlServer(configuration.GetConnectionString("ETrade"))); 22 | 23 | services.AddScoped(); 24 | services.AddScoped(); 25 | 26 | 27 | return services; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DataAccess/EntityConfigurations/CategoryConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Entities.Concretes; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace DataAccess.EntityConfigurations; 11 | 12 | public class CategoryConfiguration : IEntityTypeConfiguration 13 | { 14 | public void Configure(EntityTypeBuilder builder) 15 | { 16 | builder.ToTable("Categories").HasKey(b => b.Id); 17 | 18 | builder.Property(b => b.Id).HasColumnName("CategoryId").IsRequired(); 19 | builder.Property(b => b.Name).HasColumnName("CategoryName").IsRequired(); 20 | 21 | builder.HasIndex(indexExpression: b => b.Name, name: "UK_Categories_Name").IsUnique(); 22 | 23 | builder.HasMany(b => b.Products); 24 | 25 | builder.HasQueryFilter(b => !b.DeletedDate.HasValue); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DataAccess/EntityConfigurations/ProductConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Entities.Concretes; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Microsoft.EntityFrameworkCore; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace DataAccess.EntityConfigurations; 11 | 12 | public class ProductConfiguration : IEntityTypeConfiguration 13 | { 14 | public void Configure(EntityTypeBuilder builder) 15 | { 16 | builder.ToTable("Products").HasKey(b => b.Id); 17 | 18 | builder.Property(b => b.Id).HasColumnName("ProductId").IsRequired(); 19 | builder.Property(b => b.CategoryId).HasColumnName("CategoryId"); 20 | builder.Property(b => b.ProductName).HasColumnName("ProductName").IsRequired(); 21 | builder.Property(b => b.UnitPrice).HasColumnName("UnitPrice"); 22 | builder.Property(b => b.UnitsInStock).HasColumnName("UnitsInStock"); 23 | builder.Property(b => b.QuantityPerUnit).HasColumnName("QuantityPerUnit"); 24 | 25 | builder.HasIndex(indexExpression: b => b.ProductName, name: "UK_Products_ProductName").IsUnique(); 26 | 27 | builder.HasOne(b => b.Category); 28 | 29 | builder.HasQueryFilter(b => !b.DeletedDate.HasValue); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Entities/Concretes/Category.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Entities.Concretes; 9 | 10 | public class Category:Entity 11 | { 12 | public string Name { get; set; } 13 | 14 | public List Products { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /Entities/Concretes/Product.cs: -------------------------------------------------------------------------------- 1 | using Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Entities.Concretes; 9 | 10 | public class Product:Entity 11 | { 12 | public int CategoryId { get; set; } 13 | public string ProductName { get; set; } 14 | public decimal UnitPrice { get; set; } 15 | public short UnitsInStock { get; set; } 16 | public string QuantityPerUnit { get; set; } 17 | 18 | public Category Category { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /Entities/Entities.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /WebApi/Controllers/CategoriesController.cs: -------------------------------------------------------------------------------- 1 | using Business.Abstracts; 2 | using Business.Dtos.Requests; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace WebApi.Controllers 7 | { 8 | [Route("api/[controller]")] 9 | [ApiController] 10 | public class CategoriesController : ControllerBase 11 | { 12 | ICategoryService _categoryService; 13 | 14 | public CategoriesController(ICategoryService categoryService) 15 | { 16 | _categoryService = categoryService; 17 | } 18 | 19 | [HttpPost] 20 | public async Task Add([FromBody] CreateCategoryRequest createCategoryRequest) 21 | { 22 | var result = await _categoryService.Add(createCategoryRequest); 23 | return Ok(result); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebApi/Controllers/ProductsController.cs: -------------------------------------------------------------------------------- 1 | using Business.Abstracts; 2 | using Business.Dtos.Requests; 3 | using Core.DataAccess.Paging; 4 | using Entities.Concretes; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | namespace WebApi.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | [ApiController] 12 | public class ProductsController : ControllerBase 13 | { 14 | IProductService _productService; 15 | 16 | public ProductsController(IProductService productService) 17 | { 18 | _productService = productService; 19 | } 20 | 21 | [HttpPost] 22 | public async Task Add([FromBody] CreateProductRequest createProductRequest) 23 | { 24 | var result = await _productService.Add(createProductRequest); 25 | return Ok(result); 26 | } 27 | 28 | [HttpGet] 29 | public async Task GetList([FromQuery] PageRequest pageRequest) 30 | { 31 | var result = await _productService.GetListAsync(pageRequest); 32 | return Ok(result); 33 | } 34 | } 35 | } 36 | 37 | 38 | //postrequest-->productsController:add-->business.... -------------------------------------------------------------------------------- /WebApi/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace WebApi.Controllers 4 | { 5 | [ApiController] 6 | [Route("[controller]")] 7 | public class WeatherForecastController : ControllerBase 8 | { 9 | private static readonly string[] Summaries = new[] 10 | { 11 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 12 | }; 13 | 14 | private readonly ILogger _logger; 15 | 16 | public WeatherForecastController(ILogger logger) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | [HttpGet(Name = "GetWeatherForecast")] 22 | public IEnumerable Get() 23 | { 24 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 25 | { 26 | Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), 27 | TemperatureC = Random.Shared.Next(-20, 55), 28 | Summary = Summaries[Random.Shared.Next(Summaries.Length)] 29 | }) 30 | .ToArray(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Business; 2 | using Core.CrossCuttingConcerns.Exceptions.Extensions; 3 | using DataAccess; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | // Add services to the container. 8 | 9 | builder.Services.AddControllers(); 10 | builder.Services.AddBusinessServices(); 11 | builder.Services.AddDataAccessServices(builder.Configuration); 12 | 13 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 14 | builder.Services.AddEndpointsApiExplorer(); 15 | builder.Services.AddSwaggerGen(); 16 | 17 | var app = builder.Build(); 18 | 19 | // Configure the HTTP request pipeline. 20 | if (app.Environment.IsDevelopment()) 21 | { 22 | app.UseSwagger(); 23 | app.UseSwaggerUI(); 24 | } 25 | 26 | app.ConfigureCustomExceptionMiddleware(); 27 | 28 | app.UseAuthorization(); 29 | 30 | app.MapControllers(); 31 | 32 | app.Run(); 33 | -------------------------------------------------------------------------------- /WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:58749", 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:5143", 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 | -------------------------------------------------------------------------------- /WebApi/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi 2 | { 3 | public class WeatherForecast 4 | { 5 | public DateOnly Date { get; set; } 6 | 7 | public int TemperatureC { get; set; } 8 | 9 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 10 | 11 | public string? Summary { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApi/WebApi.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 | -------------------------------------------------------------------------------- /WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "ConnectionStrings": { 9 | "ETrade" : "Server=(localdb)\\mssqllocaldb; initial catalog=Northwind; integrated security=true" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /nLayeredApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.9.34321.82 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Entities\Entities.csproj", "{C50ABBCB-5891-460C-9946-79FA20E5662A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAccess", "DataAccess\DataAccess.csproj", "{CFA9431E-E9C3-44F4-AAFB-8C26D108EFE0}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Business", "Business\Business.csproj", "{79E0B91A-6C67-4887-9397-EC787166C94F}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{4430D030-0B94-46FF-9228-6782CA63F947}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi", "WebApi\WebApi.csproj", "{C2C13C80-A9BB-4796-B856-9E8C952E7D0E}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {C50ABBCB-5891-460C-9946-79FA20E5662A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {C50ABBCB-5891-460C-9946-79FA20E5662A}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {C50ABBCB-5891-460C-9946-79FA20E5662A}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {C50ABBCB-5891-460C-9946-79FA20E5662A}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {CFA9431E-E9C3-44F4-AAFB-8C26D108EFE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {CFA9431E-E9C3-44F4-AAFB-8C26D108EFE0}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {CFA9431E-E9C3-44F4-AAFB-8C26D108EFE0}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {CFA9431E-E9C3-44F4-AAFB-8C26D108EFE0}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {79E0B91A-6C67-4887-9397-EC787166C94F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {79E0B91A-6C67-4887-9397-EC787166C94F}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {79E0B91A-6C67-4887-9397-EC787166C94F}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {79E0B91A-6C67-4887-9397-EC787166C94F}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {4430D030-0B94-46FF-9228-6782CA63F947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {4430D030-0B94-46FF-9228-6782CA63F947}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {4430D030-0B94-46FF-9228-6782CA63F947}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {4430D030-0B94-46FF-9228-6782CA63F947}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {C2C13C80-A9BB-4796-B856-9E8C952E7D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {C2C13C80-A9BB-4796-B856-9E8C952E7D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {C2C13C80-A9BB-4796-B856-9E8C952E7D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {C2C13C80-A9BB-4796-B856-9E8C952E7D0E}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {66B3A5DA-1C19-45D3-898D-ADC1B5700A37} 48 | EndGlobalSection 49 | EndGlobal 50 | --------------------------------------------------------------------------------