├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DesktopClearArhitecture.sln ├── DesktopClearArhitecture.sln.DotSettings ├── Directory.Build.Props ├── LICENSE ├── README.md ├── benchs └── DesktopClearArchitecture.Infrastructure.Benchs │ ├── DesktopClearArchitecture.Infrastructure.Benchs.csproj │ ├── Program.cs │ └── ServicesBench.cs ├── docker-compose.yml ├── src ├── Application │ └── DesktopClearArchitecture.Application │ │ ├── DesktopClearArchitecture.Application.csproj │ │ ├── Dtos │ │ └── SongDto.cs │ │ ├── Extensions │ │ └── ServiceCollectionExtension.cs │ │ └── Profiles │ │ ├── SongProfile.cs │ │ └── SongRegister.cs ├── Domain │ └── DesktopClearArchitecture.Domain │ │ ├── Abstractions │ │ ├── IGameSearcher.cs │ │ ├── IMusicPlayer.cs │ │ └── Repositories │ │ │ ├── IPagedList.cs │ │ │ ├── IRepository.cs │ │ │ ├── IRepositoryFactory.cs │ │ │ └── IUnitOfWork.cs │ │ ├── Common │ │ ├── AuditableEntity.cs │ │ ├── BaseEntity.cs │ │ └── Result │ │ │ ├── PagedList.cs │ │ │ └── SaveChangesResult.cs │ │ ├── DesktopClearArchitecture.Domain.csproj │ │ ├── Entities │ │ └── Product.cs │ │ └── Models │ │ ├── Game.cs │ │ └── Song.cs ├── Infrastructure │ ├── DesktopClearArchitecture.Infrastructure.Identity │ │ └── DesktopClearArchitecture.Infrastructure.Identity.csproj │ ├── DesktopClearArchitecture.Infrastructure.Persistence │ │ ├── Configurations │ │ │ └── ProductConfiguration.cs │ │ ├── Contexts │ │ │ └── ApplicationDbContext.cs │ │ ├── DesktopClearArchitecture.Infrastructure.Persistence.csproj │ │ ├── Extensions │ │ │ ├── PagedListExtension.cs │ │ │ └── ServiceCollectionExtension.cs │ │ ├── Factories │ │ │ └── ApplicationDbContextFactory.cs │ │ └── Repositories │ │ │ ├── Repository.cs │ │ │ └── UnitOfWork.cs │ └── DesktopClearArchitecture.Infrastructure │ │ ├── DesktopClearArchitecture.Infrastructure.csproj │ │ ├── Models │ │ ├── GameList.cs │ │ └── Root.cs │ │ └── Services │ │ ├── GameSearcher.cs │ │ └── MusicPlayer.cs └── UI │ ├── DesktopClearArchitecture.Client │ ├── App.xaml │ ├── App.xaml.cs │ ├── DesktopClearArchitecture.Client.csproj │ ├── Models │ │ └── DataNavigationView.cs │ ├── ViewModels │ │ └── MainWindowViewModel.cs │ ├── Views │ │ ├── MainWindow.xaml │ │ └── MainWindow.xaml.cs │ ├── appsettings.Development.json │ ├── appsettings.Production.json │ ├── appsettings.Stage.json │ └── appsettings.json │ ├── DesktopClearArchitecture.Shared │ ├── Constants │ │ └── RegionNames.cs │ ├── Convertors │ │ └── CelsiusToFahrenheitConvertor.cs │ ├── DesktopClearArchitecture.Shared.csproj │ └── ViewModels │ │ ├── DialogViewModelBase.cs │ │ ├── NavigationViewModelBase.cs │ │ └── ViewModelBase.cs │ ├── Dialogs │ └── DesktopClearArchitecture.UI.Dialogs.Authorization │ │ ├── DesktopClearArchitecture.UI.Dialogs.Authorization.csproj │ │ ├── ViewModels │ │ └── AuthorizationControlViewModel.cs │ │ └── Views │ │ ├── AuthorizationControl.xaml │ │ └── AuthorizationControl.xaml.cs │ └── Modules │ ├── DesktopClearArchitecture.UI.Modules.Games │ ├── DesktopClearArchitecture.UI.Modules.Games.csproj │ ├── GamesModule.cs │ ├── ViewModels │ │ └── GamesControlViewModel.cs │ └── Views │ │ ├── GamesControl.xaml │ │ └── GamesControl.xaml.cs │ ├── DesktopClearArchitecture.UI.Modules.Home │ ├── DesktopClearArchitecture.UI.Modules.Home.csproj │ ├── HomeModule.cs │ ├── ViewModels │ │ └── HomeControlViewModel.cs │ └── Views │ │ ├── HomeControl.xaml │ │ └── HomeControl.xaml.cs │ ├── DesktopClearArchitecture.UI.Modules.Music │ ├── DesktopClearArchitecture.UI.Modules.Music.csproj │ ├── MusicModule.cs │ ├── ViewModels │ │ └── MusicControlViewModel.cs │ └── Views │ │ ├── MusicControl.xaml │ │ └── MusicControl.xaml.cs │ ├── DesktopClearArchitecture.UI.Modules.Products │ ├── DesktopClearArchitecture.UI.Modules.Products.csproj │ ├── ProductsModule.cs │ ├── ViewModels │ │ └── ProductsControlViewModel.cs │ └── Views │ │ ├── ProductsControl.xaml │ │ └── ProductsControl.xaml.cs │ └── DesktopClearArchitecture.UI.Modules.Settings │ ├── DesktopClearArchitecture.UI.Modules.Settings.csproj │ ├── SettingsModule.cs │ ├── ViewModels │ └── SettingsControlViewModel.cs │ └── Views │ ├── SettingsControl.xaml │ └── SettingsControl.xaml.cs ├── stylecop.ruleset ├── stylecopTests.ruleset └── tests ├── DesktopClearArchitecture.Architecture.Tests ├── ArchitectureTests.cs └── DesktopClearArchitecture.Architecture.Tests.csproj ├── DesktopClearArchitecture.Infrasctucture.Identity.Tests └── DesktopClearArchitecture.Infrasctucture.Identity.Tests.csproj └── DesktopClearArchitecture.UI.Shared.Tests ├── ConvertorsTests.cs └── DesktopClearArchitecture.UI.Shared.Tests.csproj /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | 4 | # This continuous integration pipeline is triggered anytime a user pushes code to the repo. 5 | # This pipeline builds the Wpf project, runs unit tests, then saves the MSIX build artifact. 6 | name: Wpf Continuous Integration 7 | 8 | # Trigger on every master branch push and pull request 9 | on: 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | branches: 15 | - master 16 | 17 | jobs: 18 | 19 | build: 20 | 21 | strategy: 22 | matrix: 23 | targetplatform: [x86, x64] 24 | 25 | runs-on: windows-latest 26 | 27 | env: 28 | App_Packages_Directory: AppPackages 29 | SigningCertificate: GitHubActionsDemo.pfx 30 | Solution_Path: DesktopClearArhitecture.sln 31 | Test_Project_Path: tests\DesktopClearArchitecture.Infrasctucture.Identity.Test\DesktopClearArchitecture.Infrasctucture.Identity.Test.csproj 32 | Wpf_Project_Path: src\UI\DesktopClearArchitecture.Client\DesktopClearArchitecture.Client.csproj 33 | Wap_Project_Directory: MyWpfApp.Package 34 | Wap_Project_Name: MyWpfApp.Package.wapproj 35 | Actions_Allow_Unsecure_Commands: true # Allows AddPAth and SetEnv commands 36 | 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v2 40 | with: 41 | fetch-depth: 0 # avoid shallow clone so nbgv can do its work. 42 | 43 | # Use Nerdbank.GitVersioning to set version variables: https://github.com/AArnott/nbgv 44 | - name: Use Nerdbank.GitVersioning to set version variables 45 | uses: aarnott/nbgv@v0.3 46 | with: 47 | setAllVars: true 48 | 49 | # Install the .NET Core workload 50 | - name: Install .NET Core 51 | uses: actions/setup-dotnet@v1 52 | with: 53 | dotnet-version: '3.1.302' 54 | 55 | # Add MsBuild to the PATH: https://github.com/microsoft/setup-msbuild 56 | - name: Setup MSBuild.exe 57 | uses: microsoft/setup-msbuild@v1.0.1 58 | 59 | # Update the version before build 60 | - name: Update manifest version 61 | run: | 62 | [xml]$manifest = get-content ".\$env:Wap_Project_Directory\Package.appxmanifest" 63 | $manifest.Package.Identity.Version = "$env:NBGV_SimpleVersion.0" 64 | $manifest.save(".\$env:Wap_Project_Directory\Package.appxmanifest") 65 | 66 | # Test 67 | - name: Execute Unit Tests 68 | run: dotnet test $env:Test_Project_Path 69 | 70 | # Restore the application 71 | - name: Restore the Wpf application to populate the obj folder 72 | run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier 73 | env: 74 | Configuration: Debug 75 | RuntimeIdentifier: win-${{ matrix.targetplatform }} 76 | 77 | # Decode the Base64 encoded Pfx 78 | - name: Decode the Pfx 79 | run: | 80 | $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") 81 | $currentDirectory = Get-Location 82 | $certificatePath = Join-Path -Path $currentDirectory -ChildPath $env:Wap_Project_Directory -AdditionalChildPath $env:SigningCertificate 83 | [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte) 84 | 85 | # Build the Windows Application Packaging project 86 | - name: Build the Windows Application Packaging Project (wapproj) 87 | run: msbuild $env:Solution_Path /p:Platform=$env:TargetPlatform /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:BuildMode /p:AppxBundle=$env:AppxBundle /p:PackageCertificateKeyFile=$env:SigningCertificate /p:PackageCertificatePassword=${{ secrets.Pfx_Key }} 88 | env: 89 | AppxBundle: Never 90 | BuildMode: SideloadOnly 91 | Configuration: Debug 92 | TargetPlatform: ${{ matrix.targetplatform }} 93 | 94 | # Remove the .pfx 95 | - name: Remove the .pfx 96 | run: Remove-Item -path $env:Wap_Project_Directory\$env:SigningCertificate 97 | 98 | # Upload the MSIX package: https://github.com/marketplace/actions/upload-artifact 99 | - name: Upload build artifacts 100 | uses: actions/upload-artifact@v1 101 | with: 102 | name: MSIX Package 103 | path: ${{ env.Wap_Project_Directory }}\${{ env.App_Packages_Directory }} 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### VisualStudio template 2 | ## Ignore Visual Studio temporary files, build results, and 3 | ## files generated by popular Visual Studio add-ons. 4 | ## 5 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 6 | 7 | # User-specific files 8 | *.rsuser 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Ww][Ii][Nn]32/ 28 | [Aa][Rr][Mm]/ 29 | [Aa][Rr][Mm]64/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 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 364 | 365 | ### JetBrains template 366 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 367 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 368 | 369 | # User-specific stuff 370 | .idea/**/workspace.xml 371 | .idea/**/tasks.xml 372 | .idea/**/usage.statistics.xml 373 | .idea/**/dictionaries 374 | .idea/**/shelf 375 | 376 | # Generated files 377 | .idea/**/contentModel.xml 378 | 379 | # Sensitive or high-churn files 380 | .idea/**/dataSources/ 381 | .idea/**/dataSources.ids 382 | .idea/**/dataSources.local.xml 383 | .idea/**/sqlDataSources.xml 384 | .idea/**/dynamic.xml 385 | .idea/**/uiDesigner.xml 386 | .idea/**/dbnavigator.xml 387 | 388 | # Gradle 389 | .idea/**/gradle.xml 390 | .idea/**/libraries 391 | 392 | # Gradle and Maven with auto-import 393 | # When using Gradle or Maven with auto-import, you should exclude module files, 394 | # since they will be recreated, and may cause churn. Uncomment if using 395 | # auto-import. 396 | # .idea/artifacts 397 | # .idea/compiler.xml 398 | # .idea/jarRepositories.xml 399 | # .idea/modules.xml 400 | # .idea/*.iml 401 | # .idea/modules 402 | # *.iml 403 | # *.ipr 404 | 405 | # CMake 406 | cmake-build-*/ 407 | 408 | # Mongo Explorer plugin 409 | .idea/**/mongoSettings.xml 410 | 411 | # File-based project format 412 | *.iws 413 | 414 | # IntelliJ 415 | out/ 416 | 417 | # mpeltonen/sbt-idea plugin 418 | .idea_modules/ 419 | 420 | # JIRA plugin 421 | atlassian-ide-plugin.xml 422 | 423 | # Cursive Clojure plugin 424 | .idea/replstate.xml 425 | 426 | # Crashlytics plugin (for Android Studio and IntelliJ) 427 | com_crashlytics_export_strings.xml 428 | crashlytics.properties 429 | crashlytics-build.properties 430 | fabric.properties 431 | 432 | # Editor-based Rest Client 433 | .idea/httpRequests 434 | 435 | # Android studio 3.1+ serialized cache file 436 | .idea/caches/build_file_checksums.ser 437 | 438 | ### Windows template 439 | # Windows thumbnail cache files 440 | Thumbs.db 441 | Thumbs.db:encryptable 442 | ehthumbs.db 443 | ehthumbs_vista.db 444 | 445 | # Dump file 446 | *.stackdump 447 | 448 | # Folder config file 449 | [Dd]esktop.ini 450 | 451 | # Recycle Bin used on file shares 452 | $RECYCLE.BIN/ 453 | 454 | # Windows Installer files 455 | *.cab 456 | *.msi 457 | *.msix 458 | *.msm 459 | *.msp 460 | 461 | # Windows shortcuts 462 | *.lnk 463 | 464 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | lewshadow@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | If you want to contribute to a project and make it better, your help is very welcome. Contributing is also a great way to learn more about social coding on Github, new technologies and and their ecosystems and how to make constructive, helpful bug reports, feature requests and the noblest of all contributions: a good, clean pull request. 4 | 5 | ### How to make a clean pull request 6 | 7 | Look for a project's contribution instructions. If there are any, follow them. 8 | 9 | - Create a personal fork of the project on Github. 10 | - Clone the fork on your local machine. Your remote repo on Github is called `origin`. 11 | - Add the original repository as a remote called `upstream`. 12 | - If you created your fork a while ago be sure to pull upstream changes into your local repository. 13 | - Create a new branch to work on! Branch from `develop` if it exists, else from `master`. 14 | - Implement/fix your feature, comment your code. 15 | - Follow the code style of the project, including indentation. 16 | - If the project has tests run them! 17 | - Write or adapt tests as needed. 18 | - Add or change the documentation as needed. 19 | - Squash your commits into a single commit with git's [interactive rebase](https://help.github.com/articles/interactive-rebase). Create a new branch if necessary. 20 | - Push your branch to your fork on Github, the remote `origin`. 21 | - From your fork open a pull request in the correct branch. Target the project's `develop` branch if there is one, else go for `master`! 22 | - Once the pull request is approved and merged you can pull the changes from `upstream` to your local repo and delete 23 | your extra branch(es). 24 | 25 | And last but not least: Always write your commit messages in the present tense. Your commit message should describe what the commit, when applied, does to the code – not what you did to the code. 26 | -------------------------------------------------------------------------------- /DesktopClearArhitecture.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2085163C-7BCB-4625-A63F-AD9EE86FD24F}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C88C0AF8-A3A8-4E2B-BC32-114B411857C0}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{F19B327B-0830-46BF-B041-78ADA2077D64}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{6CB53B6A-C702-4029-BE9A-8CEDB492FA80}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{1BD55B3E-5CF3-4E1C-89B9-AA9C3AE6931E}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solutionFolder", "solutionFolder", "{E5001530-F5A5-480A-8BAD-5487F6069985}" 17 | ProjectSection(SolutionItems) = preProject 18 | .gitignore = .gitignore 19 | README.md = README.md 20 | stylecop.ruleset = stylecop.ruleset 21 | stylecopTests.ruleset = stylecopTests.ruleset 22 | Directory.Build.Props = Directory.Build.Props 23 | EndProjectSection 24 | EndProject 25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Domain", "src\Domain\DesktopClearArchitecture.Domain\DesktopClearArchitecture.Domain.csproj", "{37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}" 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Application", "src\Application\DesktopClearArchitecture.Application\DesktopClearArchitecture.Application.csproj", "{CA84664A-5386-4070-9A72-AC77DB5280AD}" 28 | EndProject 29 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Infrastructure", "src\Infrastructure\DesktopClearArchitecture.Infrastructure\DesktopClearArchitecture.Infrastructure.csproj", "{3A2605DB-CCF0-4982-9092-117D6E264E3A}" 30 | EndProject 31 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Infrasctucture.Identity.Tests", "tests\DesktopClearArchitecture.Infrasctucture.Identity.Tests\DesktopClearArchitecture.Infrasctucture.Identity.Tests.csproj", "{03656BAD-7F6A-47D6-BD64-8B788E60EB69}" 32 | EndProject 33 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{02E9CD84-7D81-4943-828D-C7D6DF3C4863}" 34 | EndProject 35 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Infrastructure.Persistence", "src\Infrastructure\DesktopClearArchitecture.Infrastructure.Persistence\DesktopClearArchitecture.Infrastructure.Persistence.csproj", "{6E2F3FF3-2DB3-4914-930E-59987E184A58}" 36 | EndProject 37 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Infrastructure.Identity", "src\Infrastructure\DesktopClearArchitecture.Infrastructure.Identity\DesktopClearArchitecture.Infrastructure.Identity.csproj", "{54D97E45-20DA-4581-BF86-19A52D11DFCC}" 38 | EndProject 39 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Client", "src\UI\DesktopClearArchitecture.Client\DesktopClearArchitecture.Client.csproj", "{CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}" 40 | EndProject 41 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{3F6E1163-17D1-45ED-91FA-6142F89A87BD}" 42 | EndProject 43 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Shared", "src\UI\DesktopClearArchitecture.Shared\DesktopClearArchitecture.Shared.csproj", "{4EEFA1F0-0D10-4943-A281-9E80619BCB1F}" 44 | EndProject 45 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.UI.Modules.Home", "src\UI\Modules\DesktopClearArchitecture.UI.Modules.Home\DesktopClearArchitecture.UI.Modules.Home.csproj", "{4A542268-FBE1-4E0D-9EC4-77F64E6739FE}" 46 | EndProject 47 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.UI.Modules.Products", "src\UI\Modules\DesktopClearArchitecture.UI.Modules.Products\DesktopClearArchitecture.UI.Modules.Products.csproj", "{C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}" 48 | EndProject 49 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.UI.Modules.Music", "src\UI\Modules\DesktopClearArchitecture.UI.Modules.Music\DesktopClearArchitecture.UI.Modules.Music.csproj", "{948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}" 50 | EndProject 51 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.UI.Modules.Games", "src\UI\Modules\DesktopClearArchitecture.UI.Modules.Games\DesktopClearArchitecture.UI.Modules.Games.csproj", "{F488DF66-E0F4-4202-917F-00ECDE305644}" 52 | EndProject 53 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.UI.Modules.Settings", "src\UI\Modules\DesktopClearArchitecture.UI.Modules.Settings\DesktopClearArchitecture.UI.Modules.Settings.csproj", "{7EAB2162-1DE7-4986-B699-04E67D99B865}" 54 | EndProject 55 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchs", "benchs", "{2A0EDE92-5EF3-456C-A2F3-7F0B3CE11F12}" 56 | EndProject 57 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Infrastructure.Benchs", "benchs\DesktopClearArchitecture.Infrastructure.Benchs\DesktopClearArchitecture.Infrastructure.Benchs.csproj", "{0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}" 58 | EndProject 59 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{41240DE6-89B7-4748-8936-A64108B22FD7}" 60 | ProjectSection(SolutionItems) = preProject 61 | docker-compose.yml = docker-compose.yml 62 | EndProjectSection 63 | EndProject 64 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dialogs", "Dialogs", "{159E30A1-3A11-4E91-84BB-CC4FCFB024B6}" 65 | EndProject 66 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.UI.Dialogs.Authorization", "src\UI\Dialogs\DesktopClearArchitecture.UI.Dialogs.Authorization\DesktopClearArchitecture.UI.Dialogs.Authorization.csproj", "{7A199108-9CEF-4326-B8BE-42A9A42161D3}" 67 | EndProject 68 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.UI.Shared.Tests", "tests\DesktopClearArchitecture.UI.Shared.Tests\DesktopClearArchitecture.UI.Shared.Tests.csproj", "{3FD5B281-06EE-4D74-9158-1C7D91FB69C0}" 69 | EndProject 70 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesktopClearArchitecture.Architecture.Tests", "tests\DesktopClearArchitecture.Architecture.Tests\DesktopClearArchitecture.Architecture.Tests.csproj", "{24A3877F-80BB-4EB2-A921-F8D3E6660EAD}" 71 | EndProject 72 | Global 73 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 74 | Debug|Any CPU = Debug|Any CPU 75 | Debug|x64 = Debug|x64 76 | Debug|x86 = Debug|x86 77 | Release|Any CPU = Release|Any CPU 78 | Release|x64 = Release|x64 79 | Release|x86 = Release|x86 80 | EndGlobalSection 81 | GlobalSection(SolutionProperties) = preSolution 82 | HideSolutionNode = FALSE 83 | EndGlobalSection 84 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 85 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 86 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Debug|Any CPU.Build.0 = Debug|Any CPU 87 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Debug|x64.ActiveCfg = Debug|Any CPU 88 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Debug|x64.Build.0 = Debug|Any CPU 89 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Debug|x86.ActiveCfg = Debug|Any CPU 90 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Debug|x86.Build.0 = Debug|Any CPU 91 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Release|Any CPU.ActiveCfg = Release|Any CPU 92 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Release|Any CPU.Build.0 = Release|Any CPU 93 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Release|x64.ActiveCfg = Release|Any CPU 94 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Release|x64.Build.0 = Release|Any CPU 95 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Release|x86.ActiveCfg = Release|Any CPU 96 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30}.Release|x86.Build.0 = Release|Any CPU 97 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 98 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Debug|Any CPU.Build.0 = Debug|Any CPU 99 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Debug|x64.ActiveCfg = Debug|Any CPU 100 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Debug|x64.Build.0 = Debug|Any CPU 101 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Debug|x86.ActiveCfg = Debug|Any CPU 102 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Debug|x86.Build.0 = Debug|Any CPU 103 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Release|Any CPU.ActiveCfg = Release|Any CPU 104 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Release|Any CPU.Build.0 = Release|Any CPU 105 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Release|x64.ActiveCfg = Release|Any CPU 106 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Release|x64.Build.0 = Release|Any CPU 107 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Release|x86.ActiveCfg = Release|Any CPU 108 | {CA84664A-5386-4070-9A72-AC77DB5280AD}.Release|x86.Build.0 = Release|Any CPU 109 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 110 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Debug|Any CPU.Build.0 = Debug|Any CPU 111 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Debug|x64.ActiveCfg = Debug|Any CPU 112 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Debug|x64.Build.0 = Debug|Any CPU 113 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Debug|x86.ActiveCfg = Debug|Any CPU 114 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Debug|x86.Build.0 = Debug|Any CPU 115 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Release|Any CPU.ActiveCfg = Release|Any CPU 116 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Release|Any CPU.Build.0 = Release|Any CPU 117 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Release|x64.ActiveCfg = Release|Any CPU 118 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Release|x64.Build.0 = Release|Any CPU 119 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Release|x86.ActiveCfg = Release|Any CPU 120 | {3A2605DB-CCF0-4982-9092-117D6E264E3A}.Release|x86.Build.0 = Release|Any CPU 121 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 122 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Debug|Any CPU.Build.0 = Debug|Any CPU 123 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Debug|x64.ActiveCfg = Debug|Any CPU 124 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Debug|x64.Build.0 = Debug|Any CPU 125 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Debug|x86.ActiveCfg = Debug|Any CPU 126 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Debug|x86.Build.0 = Debug|Any CPU 127 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Release|Any CPU.ActiveCfg = Release|Any CPU 128 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Release|Any CPU.Build.0 = Release|Any CPU 129 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Release|x64.ActiveCfg = Release|Any CPU 130 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Release|x64.Build.0 = Release|Any CPU 131 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Release|x86.ActiveCfg = Release|Any CPU 132 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69}.Release|x86.Build.0 = Release|Any CPU 133 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 134 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Debug|Any CPU.Build.0 = Debug|Any CPU 135 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Debug|x64.ActiveCfg = Debug|Any CPU 136 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Debug|x64.Build.0 = Debug|Any CPU 137 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Debug|x86.ActiveCfg = Debug|Any CPU 138 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Debug|x86.Build.0 = Debug|Any CPU 139 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Release|Any CPU.ActiveCfg = Release|Any CPU 140 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Release|Any CPU.Build.0 = Release|Any CPU 141 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Release|x64.ActiveCfg = Release|Any CPU 142 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Release|x64.Build.0 = Release|Any CPU 143 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Release|x86.ActiveCfg = Release|Any CPU 144 | {6E2F3FF3-2DB3-4914-930E-59987E184A58}.Release|x86.Build.0 = Release|Any CPU 145 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 146 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Debug|Any CPU.Build.0 = Debug|Any CPU 147 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Debug|x64.ActiveCfg = Debug|Any CPU 148 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Debug|x64.Build.0 = Debug|Any CPU 149 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Debug|x86.ActiveCfg = Debug|Any CPU 150 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Debug|x86.Build.0 = Debug|Any CPU 151 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Release|Any CPU.ActiveCfg = Release|Any CPU 152 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Release|Any CPU.Build.0 = Release|Any CPU 153 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Release|x64.ActiveCfg = Release|Any CPU 154 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Release|x64.Build.0 = Release|Any CPU 155 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Release|x86.ActiveCfg = Release|Any CPU 156 | {54D97E45-20DA-4581-BF86-19A52D11DFCC}.Release|x86.Build.0 = Release|Any CPU 157 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 158 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Debug|Any CPU.Build.0 = Debug|Any CPU 159 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Debug|x64.ActiveCfg = Debug|Any CPU 160 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Debug|x64.Build.0 = Debug|Any CPU 161 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Debug|x86.ActiveCfg = Debug|Any CPU 162 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Debug|x86.Build.0 = Debug|Any CPU 163 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Release|Any CPU.ActiveCfg = Release|Any CPU 164 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Release|Any CPU.Build.0 = Release|Any CPU 165 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Release|x64.ActiveCfg = Release|Any CPU 166 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Release|x64.Build.0 = Release|Any CPU 167 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Release|x86.ActiveCfg = Release|Any CPU 168 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579}.Release|x86.Build.0 = Release|Any CPU 169 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 170 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU 171 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Debug|x64.ActiveCfg = Debug|Any CPU 172 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Debug|x64.Build.0 = Debug|Any CPU 173 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Debug|x86.ActiveCfg = Debug|Any CPU 174 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Debug|x86.Build.0 = Debug|Any CPU 175 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU 176 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Release|Any CPU.Build.0 = Release|Any CPU 177 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Release|x64.ActiveCfg = Release|Any CPU 178 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Release|x64.Build.0 = Release|Any CPU 179 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Release|x86.ActiveCfg = Release|Any CPU 180 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F}.Release|x86.Build.0 = Release|Any CPU 181 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 182 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Debug|Any CPU.Build.0 = Debug|Any CPU 183 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Debug|x64.ActiveCfg = Debug|Any CPU 184 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Debug|x64.Build.0 = Debug|Any CPU 185 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Debug|x86.ActiveCfg = Debug|Any CPU 186 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Debug|x86.Build.0 = Debug|Any CPU 187 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Release|Any CPU.ActiveCfg = Release|Any CPU 188 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Release|Any CPU.Build.0 = Release|Any CPU 189 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Release|x64.ActiveCfg = Release|Any CPU 190 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Release|x64.Build.0 = Release|Any CPU 191 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Release|x86.ActiveCfg = Release|Any CPU 192 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE}.Release|x86.Build.0 = Release|Any CPU 193 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 194 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Debug|Any CPU.Build.0 = Debug|Any CPU 195 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Debug|x64.ActiveCfg = Debug|Any CPU 196 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Debug|x64.Build.0 = Debug|Any CPU 197 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Debug|x86.ActiveCfg = Debug|Any CPU 198 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Debug|x86.Build.0 = Debug|Any CPU 199 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Release|Any CPU.ActiveCfg = Release|Any CPU 200 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Release|Any CPU.Build.0 = Release|Any CPU 201 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Release|x64.ActiveCfg = Release|Any CPU 202 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Release|x64.Build.0 = Release|Any CPU 203 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Release|x86.ActiveCfg = Release|Any CPU 204 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6}.Release|x86.Build.0 = Release|Any CPU 205 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 206 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Debug|Any CPU.Build.0 = Debug|Any CPU 207 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Debug|x64.ActiveCfg = Debug|Any CPU 208 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Debug|x64.Build.0 = Debug|Any CPU 209 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Debug|x86.ActiveCfg = Debug|Any CPU 210 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Debug|x86.Build.0 = Debug|Any CPU 211 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Release|Any CPU.ActiveCfg = Release|Any CPU 212 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Release|Any CPU.Build.0 = Release|Any CPU 213 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Release|x64.ActiveCfg = Release|Any CPU 214 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Release|x64.Build.0 = Release|Any CPU 215 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Release|x86.ActiveCfg = Release|Any CPU 216 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5}.Release|x86.Build.0 = Release|Any CPU 217 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 218 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Debug|Any CPU.Build.0 = Debug|Any CPU 219 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Debug|x64.ActiveCfg = Debug|Any CPU 220 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Debug|x64.Build.0 = Debug|Any CPU 221 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Debug|x86.ActiveCfg = Debug|Any CPU 222 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Debug|x86.Build.0 = Debug|Any CPU 223 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Release|Any CPU.ActiveCfg = Release|Any CPU 224 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Release|Any CPU.Build.0 = Release|Any CPU 225 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Release|x64.ActiveCfg = Release|Any CPU 226 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Release|x64.Build.0 = Release|Any CPU 227 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Release|x86.ActiveCfg = Release|Any CPU 228 | {F488DF66-E0F4-4202-917F-00ECDE305644}.Release|x86.Build.0 = Release|Any CPU 229 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 230 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Debug|Any CPU.Build.0 = Debug|Any CPU 231 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Debug|x64.ActiveCfg = Debug|Any CPU 232 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Debug|x64.Build.0 = Debug|Any CPU 233 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Debug|x86.ActiveCfg = Debug|Any CPU 234 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Debug|x86.Build.0 = Debug|Any CPU 235 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Release|Any CPU.ActiveCfg = Release|Any CPU 236 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Release|Any CPU.Build.0 = Release|Any CPU 237 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Release|x64.ActiveCfg = Release|Any CPU 238 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Release|x64.Build.0 = Release|Any CPU 239 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Release|x86.ActiveCfg = Release|Any CPU 240 | {7EAB2162-1DE7-4986-B699-04E67D99B865}.Release|x86.Build.0 = Release|Any CPU 241 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 242 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Debug|Any CPU.Build.0 = Debug|Any CPU 243 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Debug|x64.ActiveCfg = Debug|Any CPU 244 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Debug|x64.Build.0 = Debug|Any CPU 245 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Debug|x86.ActiveCfg = Debug|Any CPU 246 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Debug|x86.Build.0 = Debug|Any CPU 247 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Release|Any CPU.ActiveCfg = Release|Any CPU 248 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Release|Any CPU.Build.0 = Release|Any CPU 249 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Release|x64.ActiveCfg = Release|Any CPU 250 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Release|x64.Build.0 = Release|Any CPU 251 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Release|x86.ActiveCfg = Release|Any CPU 252 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D}.Release|x86.Build.0 = Release|Any CPU 253 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 254 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 255 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Debug|x64.ActiveCfg = Debug|Any CPU 256 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Debug|x64.Build.0 = Debug|Any CPU 257 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Debug|x86.ActiveCfg = Debug|Any CPU 258 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Debug|x86.Build.0 = Debug|Any CPU 259 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 260 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Release|Any CPU.Build.0 = Release|Any CPU 261 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Release|x64.ActiveCfg = Release|Any CPU 262 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Release|x64.Build.0 = Release|Any CPU 263 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Release|x86.ActiveCfg = Release|Any CPU 264 | {7A199108-9CEF-4326-B8BE-42A9A42161D3}.Release|x86.Build.0 = Release|Any CPU 265 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 266 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Debug|Any CPU.Build.0 = Debug|Any CPU 267 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Debug|x64.ActiveCfg = Debug|Any CPU 268 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Debug|x64.Build.0 = Debug|Any CPU 269 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Debug|x86.ActiveCfg = Debug|Any CPU 270 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Debug|x86.Build.0 = Debug|Any CPU 271 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Release|Any CPU.ActiveCfg = Release|Any CPU 272 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Release|Any CPU.Build.0 = Release|Any CPU 273 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Release|x64.ActiveCfg = Release|Any CPU 274 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Release|x64.Build.0 = Release|Any CPU 275 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Release|x86.ActiveCfg = Release|Any CPU 276 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0}.Release|x86.Build.0 = Release|Any CPU 277 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 278 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Debug|Any CPU.Build.0 = Debug|Any CPU 279 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Debug|x64.ActiveCfg = Debug|Any CPU 280 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Debug|x64.Build.0 = Debug|Any CPU 281 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Debug|x86.ActiveCfg = Debug|Any CPU 282 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Debug|x86.Build.0 = Debug|Any CPU 283 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Release|Any CPU.ActiveCfg = Release|Any CPU 284 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Release|Any CPU.Build.0 = Release|Any CPU 285 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Release|x64.ActiveCfg = Release|Any CPU 286 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Release|x64.Build.0 = Release|Any CPU 287 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Release|x86.ActiveCfg = Release|Any CPU 288 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD}.Release|x86.Build.0 = Release|Any CPU 289 | EndGlobalSection 290 | GlobalSection(NestedProjects) = preSolution 291 | {F19B327B-0830-46BF-B041-78ADA2077D64} = {2085163C-7BCB-4625-A63F-AD9EE86FD24F} 292 | {6CB53B6A-C702-4029-BE9A-8CEDB492FA80} = {2085163C-7BCB-4625-A63F-AD9EE86FD24F} 293 | {1BD55B3E-5CF3-4E1C-89B9-AA9C3AE6931E} = {2085163C-7BCB-4625-A63F-AD9EE86FD24F} 294 | {37B48BD7-CB1F-47F0-99C0-0FA7486D8D30} = {F19B327B-0830-46BF-B041-78ADA2077D64} 295 | {CA84664A-5386-4070-9A72-AC77DB5280AD} = {1BD55B3E-5CF3-4E1C-89B9-AA9C3AE6931E} 296 | {3A2605DB-CCF0-4982-9092-117D6E264E3A} = {6CB53B6A-C702-4029-BE9A-8CEDB492FA80} 297 | {03656BAD-7F6A-47D6-BD64-8B788E60EB69} = {C88C0AF8-A3A8-4E2B-BC32-114B411857C0} 298 | {02E9CD84-7D81-4943-828D-C7D6DF3C4863} = {2085163C-7BCB-4625-A63F-AD9EE86FD24F} 299 | {6E2F3FF3-2DB3-4914-930E-59987E184A58} = {6CB53B6A-C702-4029-BE9A-8CEDB492FA80} 300 | {54D97E45-20DA-4581-BF86-19A52D11DFCC} = {6CB53B6A-C702-4029-BE9A-8CEDB492FA80} 301 | {CCE55E0F-1DB5-4B71-AE9C-93E11B9F9579} = {02E9CD84-7D81-4943-828D-C7D6DF3C4863} 302 | {3F6E1163-17D1-45ED-91FA-6142F89A87BD} = {02E9CD84-7D81-4943-828D-C7D6DF3C4863} 303 | {4EEFA1F0-0D10-4943-A281-9E80619BCB1F} = {02E9CD84-7D81-4943-828D-C7D6DF3C4863} 304 | {4A542268-FBE1-4E0D-9EC4-77F64E6739FE} = {3F6E1163-17D1-45ED-91FA-6142F89A87BD} 305 | {C020FDA8-FE2B-4FC6-8944-4070ED51EDC6} = {3F6E1163-17D1-45ED-91FA-6142F89A87BD} 306 | {948EB0DA-668C-43B4-BFA7-0DAC4FAEF2F5} = {3F6E1163-17D1-45ED-91FA-6142F89A87BD} 307 | {F488DF66-E0F4-4202-917F-00ECDE305644} = {3F6E1163-17D1-45ED-91FA-6142F89A87BD} 308 | {7EAB2162-1DE7-4986-B699-04E67D99B865} = {3F6E1163-17D1-45ED-91FA-6142F89A87BD} 309 | {0C0FB20D-B1E6-432F-B7CD-40E7D3DF300D} = {2A0EDE92-5EF3-456C-A2F3-7F0B3CE11F12} 310 | {159E30A1-3A11-4E91-84BB-CC4FCFB024B6} = {02E9CD84-7D81-4943-828D-C7D6DF3C4863} 311 | {7A199108-9CEF-4326-B8BE-42A9A42161D3} = {159E30A1-3A11-4E91-84BB-CC4FCFB024B6} 312 | {3FD5B281-06EE-4D74-9158-1C7D91FB69C0} = {C88C0AF8-A3A8-4E2B-BC32-114B411857C0} 313 | {24A3877F-80BB-4EB2-A921-F8D3E6660EAD} = {C88C0AF8-A3A8-4E2B-BC32-114B411857C0} 314 | EndGlobalSection 315 | EndGlobal 316 | -------------------------------------------------------------------------------- /DesktopClearArhitecture.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True 12 | True -------------------------------------------------------------------------------- /Directory.Build.Props: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0-windows 4 | preview 5 | x64 6 | false 7 | ..\..\..\stylecop.ruleset 8 | true 9 | NU1701 10 | enable 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | none 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Georgy Levchenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WPF Clear Architecture Template. 2 | 3 | ## About The Project :zap: 4 | 5 | This repository is a template for building desktop applications based on a clean architecture. 6 | 7 | ## Solving common problems. 8 | 9 | * Clean application architecture. 10 | * Easy navigation (thanks to the use of NavigationView and the Prism modules). 11 | * Simple filtering and commands (Reactive Property). 12 | * Support for many extensions for IServiceCollection (AddLogging(), AddDbContex(), AddMemoryCache()). 13 | 14 | ## Tech Stack :muscle: 15 | 16 | * [WPF .NET6](https://docs.microsoft.com/ru-ru/dotnet/desktop/wpf/?view=netdesktop-6.0) 17 | * [Prism](https://prismlibrary.com/) 18 | * [ReactiveProperty](https://github.com/runceel/ReactiveProperty) 19 | * [Entity Framework Core 6](https://docs.microsoft.com/en-us/ef/core/) 20 | * [AutoMapper](https://automapper.org/) 21 | * [FluentValidation](https://fluentvalidation.net/) 22 | * [Serilog](https://serilog.net/) 23 | * [XUnit](https://xunit.net/), [FluentAssertions](https://fluentassertions.com/), [Moq](https://github.com/moq) 24 | * [Docker](https://www.docker.com/) 25 | 26 | ## Down the Roadmap 27 | 28 | * Add MediatoR 29 | * Add architectural tests 30 | 31 | ## Contributing 32 | 33 | Contributions are what make the open-source community such an amazing place to be, learn, inspire, and create. Any contributions you make are **greatly appreciated**. 34 | 35 | 1. Fork the Project 36 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 37 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 38 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 39 | 5. Open a Pull Request 40 | 41 | ## Special thanks to 42 | 43 | [![JetBrains](https://cdn.icon-icons.com/icons2/2530/PNG/512/jetbrains_rider_button_icon_151875.png)](https://www.jetbrains.com/?from=DesktopClearArchitecture) 44 | 45 | ## License 46 | 47 | Distributed under the MIT License. 48 | 49 | ## Contact 50 | ### Georgy Levchenko 51 | 52 | - 53 | - Telegram - [Georgy Levchenko](https://t.me/foxtes) 54 | 55 | ## Support :star: 56 | 57 | Has this Project helped you learn something New? or Helped you at work? Do Consider Supporting. 58 | Here are a few ways by which you can support. 59 | 60 | - Recommend this awesome project to your colleagues. 🥇 61 | - Leave your feedback 62 | 63 | -------------------------------------------------------------------------------- /benchs/DesktopClearArchitecture.Infrastructure.Benchs/DesktopClearArchitecture.Infrastructure.Benchs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | ..\..\stylecopTests.ruleset 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /benchs/DesktopClearArchitecture.Infrastructure.Benchs/Program.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Benchs; 2 | 3 | using BenchmarkDotNet.Running; 4 | 5 | /// 6 | /// Main. 7 | /// 8 | public static class Program 9 | { 10 | private static void Main() 11 | { 12 | BenchmarkRunner.Run(); 13 | } 14 | } -------------------------------------------------------------------------------- /benchs/DesktopClearArchitecture.Infrastructure.Benchs/ServicesBench.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Benchs; 2 | 3 | using AutoMapper; 4 | using Application.Dtos; 5 | using DesktopClearArchitecture.Domain.Models; 6 | using FastExpressionCompiler; 7 | using Mapster; 8 | using BenchmarkDotNet.Attributes; 9 | using Services; 10 | 11 | /// 12 | /// Bench for . 13 | /// 14 | [MemoryDiagnoser] 15 | public class ServicesBench 16 | { 17 | private readonly Mapper _mapper; 18 | private readonly MusicPlayer _musicPlayer; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public ServicesBench() 24 | { 25 | _musicPlayer = new MusicPlayer(); 26 | 27 | var config = new MapperConfiguration(cfg => cfg.CreateMap() 28 | .ForMember(dest => dest.FullName, opt => opt.MapFrom(scr => $"{scr.Name} - {scr.Duration.ToString()}"))); 29 | _mapper = new Mapper(config); 30 | 31 | TypeAdapterConfig 32 | .ForType() 33 | .Map(dest => dest.FullName, scr => $"{scr.Name} - {scr.Duration.ToString()}"); 34 | TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileFast(); 35 | } 36 | 37 | /// 38 | /// Get songs. 39 | /// 40 | [Benchmark] 41 | public void GetSongs() 42 | { 43 | _ = _musicPlayer 44 | .GetSongs() 45 | .ToArray(); 46 | } 47 | 48 | /// 49 | /// Get songs. 50 | /// 51 | [Benchmark] 52 | public void GetAutomaper() 53 | { 54 | var songs = _musicPlayer 55 | .GetSongs() 56 | .ToArray(); 57 | _ = _mapper.Map(songs); 58 | } 59 | 60 | /// 61 | /// Get songs. 62 | /// 63 | [Benchmark] 64 | public void GetSongsMapster() 65 | { 66 | var songs = _musicPlayer 67 | .GetSongs() 68 | .ToArray(); 69 | _ = songs.Adapt(); 70 | } 71 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | seq-input-gelf: 4 | image: datalust/seq-input-gelf:latest 5 | depends_on: 6 | - seq 7 | ports: 8 | - "12201:12201/udp" 9 | environment: 10 | SEQ_ADDRESS: "http://seq:5342" 11 | restart: unless-stopped 12 | 13 | seq: 14 | image: datalust/seq:latest 15 | ports: 16 | - "5342:80" 17 | environment: 18 | ACCEPT_EULA: Y 19 | restart: unless-stopped 20 | volumes: 21 | - ./seq-data:/data 22 | 23 | elasticsearch: 24 | image: docker.elastic.co/elasticsearch/elasticsearch:7.4.2 25 | environment: 26 | - discovery.type=single-node 27 | - bootstrap.memory_lock=true 28 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" 29 | ports: 30 | - "9200:9200" 31 | networks: 32 | - "elk-net" 33 | 34 | kibana: 35 | image: docker.elastic.co/kibana/kibana:7.4.2 36 | ports: 37 | - "5601:5601" 38 | networks: 39 | - "elk-net" 40 | 41 | networks: 42 | elk-net: -------------------------------------------------------------------------------- /src/Application/DesktopClearArchitecture.Application/DesktopClearArchitecture.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Application/DesktopClearArchitecture.Application/Dtos/SongDto.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Application.Dtos; 2 | 3 | /// 4 | /// Song dto. 5 | /// 6 | public readonly record struct SongDto 7 | { 8 | /// 9 | /// Full name song. 10 | /// 11 | public string FullName { get; init; } 12 | 13 | /// 14 | public override string ToString() => FullName; 15 | } -------------------------------------------------------------------------------- /src/Application/DesktopClearArchitecture.Application/Extensions/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Application.Extensions; 2 | 3 | using System.Reflection; 4 | using FastExpressionCompiler; 5 | using Mapster; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | /// 9 | /// Extension for . 10 | /// 11 | public static class ServiceCollectionExtension 12 | { 13 | /// 14 | /// Added automapper. 15 | /// 16 | /// . 17 | public static IServiceCollection AddAutomapper(this IServiceCollection services) 18 | { 19 | services.AddAutoMapper(Assembly.GetExecutingAssembly()); 20 | 21 | return services; 22 | } 23 | 24 | /// 25 | /// Added mapster. 26 | /// 27 | /// . 28 | public static IServiceCollection AddMapster(this IServiceCollection services) 29 | { 30 | TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileFast(); 31 | TypeAdapterConfig.GlobalSettings.Scan(Assembly.GetExecutingAssembly()); 32 | 33 | return services; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Application/DesktopClearArchitecture.Application/Profiles/SongProfile.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Application.Profiles; 2 | 3 | using AutoMapper; 4 | using Domain.Models; 5 | using Dtos; 6 | 7 | /// 8 | /// Song profile. 9 | /// 10 | public class SongProfile : Profile 11 | { 12 | /// 13 | public SongProfile() 14 | { 15 | CreateMap() 16 | .ForMember(dest => dest.FullName, opt => opt.MapFrom(scr => $"{scr.Name} - {scr.Duration.ToString()}")); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Application/DesktopClearArchitecture.Application/Profiles/SongRegister.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Application.Profiles; 2 | 3 | using Domain.Models; 4 | using Dtos; 5 | using Mapster; 6 | 7 | /// 8 | /// Song register. 9 | /// 10 | public class SongRegister : IRegister 11 | { 12 | /// 13 | public void Register(TypeAdapterConfig config) 14 | { 15 | config.ForType() 16 | .Map(dest => dest.FullName, scr => $"{scr.Name} - {scr.Duration.ToString()}"); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Abstractions/IGameSearcher.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Abstractions; 2 | 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using Models; 6 | 7 | /// 8 | /// Game searcher. 9 | /// 10 | public interface IGameSearcher 11 | { 12 | /// 13 | /// Get all game on steam platform. 14 | /// 15 | Task> GetGames(); 16 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Abstractions/IMusicPlayer.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Abstractions; 2 | 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | /// 7 | /// Music player. 8 | /// 9 | public interface IMusicPlayer 10 | { 11 | /// 12 | /// Get all songs. 13 | /// 14 | IEnumerable GetSongs(); 15 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Abstractions/Repositories/IPagedList.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Abstractions.Repositories; 2 | 3 | /// 4 | /// Provides the interface(s) for paged list of any type. 5 | /// 6 | /// The type for paging. 7 | public interface IPagedList 8 | { 9 | /// 10 | /// Gets the index start value. 11 | /// 12 | /// The index start value. 13 | int IndexFrom { get; } 14 | 15 | /// 16 | /// Gets the page index (current). 17 | /// 18 | int PageIndex { get; } 19 | 20 | /// 21 | /// Gets the page size. 22 | /// 23 | int PageSize { get; } 24 | 25 | /// 26 | /// Gets the total count of the list of type . 27 | /// 28 | int TotalCount { get; } 29 | 30 | /// 31 | /// Gets the total pages. 32 | /// 33 | int TotalPages { get; } 34 | 35 | /// 36 | /// Gets the current page items. 37 | /// 38 | IList Items { get; } 39 | 40 | /// 41 | /// Gets the has previous page. 42 | /// 43 | /// The has previous page. 44 | bool HasPreviousPage { get; } 45 | 46 | /// 47 | /// Gets the has next page. 48 | /// 49 | /// The has next page. 50 | bool HasNextPage { get; } 51 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Abstractions/Repositories/IRepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Abstractions.Repositories; 2 | 3 | /// 4 | /// Defines the interfaces for interfaces. 5 | /// 6 | public interface IRepositoryFactory 7 | { 8 | /// 9 | /// Gets the specified repository for the . 10 | /// 11 | /// True if providing custom repository. 12 | /// The type of the entity. 13 | /// An instance of type inherited from interface. 14 | IRepository GetRepository(bool hasCustomRepository = false) 15 | where TEntity : class; 16 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Abstractions/Repositories/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Abstractions.Repositories; 2 | 3 | using Common.Result; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.ChangeTracking; 6 | using Microsoft.EntityFrameworkCore.Storage; 7 | 8 | /// 9 | /// Defines the interface(s) for generic unit of work. 10 | /// 11 | public interface IUnitOfWork : IUnitOfWork 12 | where TContext : DbContext 13 | { 14 | /// 15 | /// Gets the db context. 16 | /// 17 | /// The instance of type . 18 | TContext DbContext { get; } 19 | 20 | /// 21 | /// Saves all changes made in this context to the database with distributed transaction. 22 | /// 23 | /// An optional array. 24 | /// A that represents the asynchronous save operation. 25 | /// The task result contains the number of state entities written to database. 26 | Task SaveChangesAsync(params IUnitOfWork[] unitOfWorks); 27 | } 28 | 29 | /// 30 | /// Defines the interface(s) for unit of work. 31 | /// 32 | public interface IUnitOfWork : IDisposable 33 | { 34 | /// 35 | /// Last error after SaveChanges operation executed. 36 | /// 37 | SaveChangesResult LastSaveChangesResult { get; } 38 | 39 | /// 40 | /// Gets the specified repository for the . 41 | /// 42 | /// True if providing custom repository. 43 | /// The type of the entity. 44 | /// An instance of type inherited from interface. 45 | IRepository GetRepository(bool hasCustomRepository = false) 46 | where TEntity : class; 47 | 48 | /// 49 | /// Saves all changes made in this context to the database. 50 | /// 51 | /// The number of state entries written to the database. 52 | int SaveChanges(); 53 | 54 | /// 55 | /// Asynchronously saves all changes made in this unit of work to the database. 56 | /// 57 | /// A that represents the asynchronous save operation. 58 | /// The task result contains the number of state entities written to database. 59 | Task SaveChangesAsync(); 60 | 61 | /// 62 | /// Executes the specified raw SQL command. 63 | /// 64 | /// The raw SQL. 65 | /// The parameters. 66 | /// The number of state entities written to database. 67 | int ExecuteSqlCommand(string sql, params object[] parameters); 68 | 69 | /// 70 | /// Executes the specified raw SQL command. 71 | /// 72 | /// The raw SQL. 73 | /// The parameters. 74 | /// The number of state entities written to database. 75 | Task ExecuteSqlCommandAsync(string sql, params object[] parameters); 76 | 77 | /// 78 | /// Uses raw SQL queries to fetch the specified data. 79 | /// 80 | /// The type of the entity. 81 | /// The raw SQL. 82 | /// The parameters. 83 | /// An that contains elements that satisfy 84 | /// the condition specified by raw SQL. 85 | IQueryable FromSqlRaw(string sql, params object[] parameters) 86 | where TEntity : class; 87 | 88 | /// 89 | /// Uses Track Graph Api to attach disconnected entities. 90 | /// 91 | /// Root entity. 92 | /// Delegate to convert Object's State properties to Entities entry state. 93 | void TrackGraph(object rootEntity, Action callback); 94 | 95 | /// 96 | /// Begin transaction. 97 | /// 98 | /// Use if exist. 99 | Task BeginTransactionAsync(bool useIfExists = false); 100 | 101 | /// 102 | /// Begin transaction async. 103 | /// 104 | /// Use if exist. 105 | IDbContextTransaction BeginTransaction(bool useIfExists = false); 106 | 107 | /// 108 | /// DbContext disable/enable auto detect changes. 109 | /// 110 | /// Value. 111 | void SetAutoDetectChanges(bool value); 112 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Common/AuditableEntity.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Common; 2 | 3 | using System; 4 | 5 | /// 6 | /// Auditable entity. 7 | /// 8 | public abstract class AuditableEntity : BaseEntity 9 | { 10 | /// 11 | /// Create by. 12 | /// 13 | public string CreatedBy { get; set; } 14 | 15 | /// 16 | /// Data created. 17 | /// 18 | public DateTime Created { get; set; } 19 | 20 | /// 21 | /// Last modified by. 22 | /// 23 | public string LastModifiedBy { get; set; } 24 | 25 | /// 26 | /// Data last modified. 27 | /// 28 | public DateTime? LastModified { get; set; } 29 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Common/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Common; 2 | 3 | /// 4 | /// Base entity. 5 | /// 6 | public abstract class BaseEntity 7 | { 8 | /// 9 | /// Id. 10 | /// 11 | public virtual long Id { get; set; } 12 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Common/Result/PagedList.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Common.Result; 2 | 3 | using Abstractions.Repositories; 4 | 5 | /// 6 | /// Provides the implementation of the and converter. 7 | /// 8 | /// The type of the source. 9 | /// The type of the result. 10 | public record PagedList : IPagedList 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The source. 16 | /// The converter. 17 | /// The index of the page. 18 | /// The size of the page. 19 | /// The index from. 20 | public PagedList( 21 | IEnumerable source, 22 | Func, IEnumerable> converter, 23 | int pageIndex, 24 | int pageSize, 25 | int indexFrom) 26 | { 27 | if (indexFrom > pageIndex) 28 | { 29 | throw new ArgumentException( 30 | $"indexFrom: {indexFrom} > pageIndex: {pageIndex}, must indexFrom <= pageIndex"); 31 | } 32 | 33 | if (source is IQueryable queryable) 34 | { 35 | PageIndex = pageIndex; 36 | PageSize = pageSize; 37 | IndexFrom = indexFrom; 38 | TotalCount = queryable.Count(); 39 | TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize); 40 | 41 | var items = queryable 42 | .Skip((PageIndex - IndexFrom) * PageSize) 43 | .Take(PageSize) 44 | .ToArray(); 45 | 46 | Items = new List(converter(items)); 47 | } 48 | else 49 | { 50 | var enumerable = source as TSource[] ?? source.ToArray(); 51 | 52 | PageIndex = pageIndex; 53 | PageSize = pageSize; 54 | IndexFrom = indexFrom; 55 | TotalCount = enumerable.Length; 56 | TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize); 57 | 58 | var items = enumerable 59 | .Skip((PageIndex - IndexFrom) * PageSize) 60 | .Take(PageSize) 61 | .ToArray(); 62 | 63 | Items = new List(converter(items)); 64 | } 65 | } 66 | 67 | /// 68 | /// Initializes a new instance of the class. 69 | /// 70 | /// The source. 71 | /// The converter. 72 | public PagedList(IPagedList source, Func, IEnumerable> converter) 73 | { 74 | PageIndex = source.PageIndex; 75 | PageSize = source.PageSize; 76 | IndexFrom = source.IndexFrom; 77 | TotalCount = source.TotalCount; 78 | TotalPages = source.TotalPages; 79 | 80 | Items = new List(converter(source.Items)); 81 | } 82 | 83 | /// 84 | public int PageIndex { get; } 85 | 86 | /// 87 | public int PageSize { get; } 88 | 89 | /// 90 | public int TotalCount { get; } 91 | 92 | /// 93 | public int TotalPages { get; } 94 | 95 | /// 96 | public int IndexFrom { get; } 97 | 98 | /// 99 | public IList Items { get; } 100 | 101 | /// 102 | public bool HasPreviousPage => PageIndex - IndexFrom > 0; 103 | 104 | /// 105 | public bool HasNextPage => PageIndex - IndexFrom + 1 < TotalPages; 106 | 107 | /// 108 | /// Creates an empty of . 109 | /// 110 | /// The type for paging. 111 | /// An empty instance of . 112 | public static IPagedList Empty() => new PagedList(); 113 | } 114 | 115 | /// 116 | /// Represents the default implementation of the interface. 117 | /// 118 | /// The type of the data to page. 119 | public record PagedList : IPagedList 120 | { 121 | /// 122 | /// Initializes a new instance of the class. 123 | /// 124 | /// The source. 125 | /// The index of the page. 126 | /// The size of the page. 127 | /// The index from. 128 | public PagedList(IEnumerable source, int pageIndex, int pageSize, int indexFrom) 129 | { 130 | if (indexFrom > pageIndex) 131 | { 132 | throw new ArgumentException( 133 | $"indexFrom: {indexFrom} > pageIndex: {pageIndex}, must indexFrom <= pageIndex"); 134 | } 135 | 136 | if (source is IQueryable queryable) 137 | { 138 | PageIndex = pageIndex; 139 | PageSize = pageSize; 140 | IndexFrom = indexFrom; 141 | TotalCount = queryable.Count(); 142 | TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize); 143 | Items = queryable 144 | .Skip((PageIndex - IndexFrom) * PageSize) 145 | .Take(PageSize) 146 | .ToList(); 147 | } 148 | else 149 | { 150 | var enumerable = source.ToList(); 151 | 152 | PageIndex = pageIndex; 153 | PageSize = pageSize; 154 | IndexFrom = indexFrom; 155 | TotalCount = enumerable.Count; 156 | TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize); 157 | Items = enumerable 158 | .Skip((PageIndex - IndexFrom) * PageSize) 159 | .Take(PageSize) 160 | .ToList(); 161 | } 162 | } 163 | 164 | /// 165 | /// Initializes a new instance of the class. 166 | /// 167 | public PagedList() => Items = Array.Empty(); 168 | 169 | /// 170 | public int PageIndex { get; init; } 171 | 172 | /// 173 | public int PageSize { get; init; } 174 | 175 | /// 176 | public int TotalCount { get; init; } 177 | 178 | /// 179 | public int TotalPages { get; init; } 180 | 181 | /// 182 | public int IndexFrom { get; init; } 183 | 184 | /// 185 | public IList Items { get; init; } 186 | 187 | /// 188 | public bool HasPreviousPage => PageIndex - IndexFrom > 0; 189 | 190 | /// 191 | public bool HasNextPage => PageIndex - IndexFrom + 1 < TotalPages; 192 | 193 | /// 194 | /// Creates a new instance of from source of instance. 195 | /// 196 | /// The type of the result. 197 | /// The type of the source. 198 | /// The source. 199 | /// The converter. 200 | /// An instance of . 201 | public static IPagedList From( 202 | IPagedList source, 203 | Func, IEnumerable> converter) => 204 | new PagedList(source, converter); 205 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Common/Result/SaveChangesResult.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace DesktopClearArchitecture.Domain.Common.Result; 4 | 5 | /// 6 | /// Represent operation result for SaveChanges. 7 | /// 8 | public class SaveChangesResult 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public SaveChangesResult() => Messages = new List(); 14 | 15 | /// 16 | public SaveChangesResult(string message) 17 | : this() => AddMessage(message); 18 | 19 | /// 20 | /// Is Exception occupied while last operation execution. 21 | /// 22 | public bool IsOk => Exception == null; 23 | 24 | /// 25 | /// Last Exception you can find here. 26 | /// 27 | public Exception? Exception { get; set; } 28 | 29 | /// 30 | /// List of the error should appear there. 31 | /// 32 | private List Messages { get; } 33 | 34 | /// 35 | /// Adds new message to result. 36 | /// 37 | /// Message. 38 | public void AddMessage(string message) => Messages.Add(message); 39 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/DesktopClearArchitecture.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Entities/Product.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Entities; 2 | 3 | using Common; 4 | 5 | /// 6 | /// Product. 7 | /// 8 | public class Product : AuditableEntity 9 | { 10 | /// 11 | /// Name. 12 | /// 13 | public string Name { get; set; } 14 | 15 | /// 16 | public override string ToString() => Name; 17 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Models/Game.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Models; 2 | 3 | using System.Runtime.Serialization; 4 | 5 | /// 6 | /// Song. 7 | /// 8 | public readonly record struct Game 9 | { 10 | /// 11 | /// Name. 12 | /// 13 | [DataMember(Name = "appid")] 14 | public int Id { get; init; } 15 | 16 | /// 17 | /// Duration. 18 | /// 19 | [DataMember(Name = "name")] 20 | public string Name { get; init; } 21 | } -------------------------------------------------------------------------------- /src/Domain/DesktopClearArchitecture.Domain/Models/Song.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Domain.Models; 2 | 3 | /// 4 | /// Song. 5 | /// 6 | public readonly record struct Song 7 | { 8 | /// 9 | /// Name. 10 | /// 11 | public string Name { get; init; } 12 | 13 | /// 14 | /// Duration. 15 | /// 16 | public int Duration { get; init; } 17 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure.Identity/DesktopClearArchitecture.Infrastructure.Identity.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure.Persistence/Configurations/ProductConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Persistence.Configurations 2 | { 3 | using Bogus; 4 | using Domain.Entities; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 7 | 8 | /// 9 | public class ProductConfiguration : IEntityTypeConfiguration 10 | { 11 | /// 12 | public void Configure(EntityTypeBuilder builder) 13 | { 14 | builder.ToTable("Product"); 15 | 16 | var ids = 1; 17 | var stock = new Faker() 18 | .RuleFor(m => m.Id, _ => ids++) 19 | .RuleFor(m => m.Created, _ => DateTime.Now) 20 | .RuleFor(m => m.Name, f => f.Commerce.ProductName()); 21 | builder.HasData(stock.GenerateBetween(10, 100)); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure.Persistence/Contexts/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Persistence.Contexts 2 | { 3 | using Configurations; 4 | using Domain.Entities; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | /// 8 | public class ApplicationDbContext : DbContext 9 | { 10 | /// 11 | public ApplicationDbContext(DbContextOptions options) 12 | : base(options) 13 | { 14 | } 15 | 16 | /// 17 | /// Products. 18 | /// 19 | public DbSet Products { get; set; } 20 | 21 | /// 22 | protected override void OnModelCreating(ModelBuilder modelBuilder) 23 | { 24 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(ProductConfiguration).Assembly); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure.Persistence/DesktopClearArchitecture.Infrastructure.Persistence.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | all 7 | runtime; build; native; contentfiles; analyzers; buildtransitive 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure.Persistence/Extensions/PagedListExtension.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Persistence.Extensions; 2 | 3 | using DesktopClearArchitecture.Domain.Abstractions.Repositories; 4 | using Domain.Common.Result; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | /// 8 | /// Provides some extension methods for to provide paging capability. 9 | /// 10 | public static class PagedListExtension 11 | { 12 | /// 13 | /// Converts the specified source to 14 | /// by the specified and . 15 | /// 16 | /// The type of the source. 17 | /// The source to paging. 18 | /// The index of the page. 19 | /// The size of the page. 20 | /// The start index value. 21 | /// An instance of the inherited from interface. 22 | public static IPagedList ToPagedList( 23 | this IEnumerable source, 24 | int pageIndex, 25 | int pageSize, 26 | int indexFrom = 0) 27 | { 28 | return new PagedList(source, pageIndex, pageSize, indexFrom); 29 | } 30 | 31 | /// 32 | /// Converts the specified source to 33 | /// by the specified , and . 34 | /// 35 | /// The type of the source. 36 | /// The type of the result. 37 | /// The source to convert. 38 | /// 39 | /// The converter to change the to . 40 | /// 41 | /// The page index. 42 | /// The page size. 43 | /// The start index value. 44 | /// An instance of the inherited from interface. 45 | public static IPagedList ToPagedList( 46 | this IEnumerable source, 47 | Func, IEnumerable> converter, 48 | int pageIndex, 49 | int pageSize, 50 | int indexFrom = 0) 51 | { 52 | return new PagedList(source, converter, pageIndex, pageSize, indexFrom); 53 | } 54 | 55 | /// 56 | /// Converts the specified source to 57 | /// by the specified and . 58 | /// 59 | /// The type of the source. 60 | /// The source to paging. 61 | /// The index of the page. 62 | /// The size of the page. 63 | /// The start index value. 64 | /// 65 | /// A to observe while waiting for the task to complete. 66 | /// 67 | /// An instance of the inherited from interface. 68 | public static async Task> ToPagedListAsync( 69 | this IQueryable source, 70 | int pageIndex, 71 | int pageSize, 72 | int indexFrom = 0, 73 | CancellationToken cancellationToken = default) 74 | { 75 | if (indexFrom > pageIndex) 76 | { 77 | throw new ArgumentException( 78 | $"indexFrom: {indexFrom} > pageIndex: {pageIndex}, must indexFrom <= pageIndex"); 79 | } 80 | 81 | var count = await source 82 | .CountAsync(cancellationToken) 83 | .ConfigureAwait(false); 84 | var items = await source 85 | .Skip((pageIndex - indexFrom) * pageSize) 86 | .Take(pageSize) 87 | .ToListAsync(cancellationToken) 88 | .ConfigureAwait(false); 89 | 90 | var pagedList = new PagedList 91 | { 92 | PageIndex = pageIndex, 93 | PageSize = pageSize, 94 | IndexFrom = indexFrom, 95 | TotalCount = count, 96 | Items = items, 97 | TotalPages = (int)Math.Ceiling(count / (double)pageSize) 98 | }; 99 | 100 | return pagedList; 101 | } 102 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure.Persistence/Extensions/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Persistence.Extensions 2 | { 3 | using Contexts; 4 | using Domain.Abstractions.Repositories; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | using Repositories; 10 | using Serilog; 11 | 12 | /// 13 | /// Extension for . 14 | /// 15 | public static class ServiceCollectionExtension 16 | { 17 | /// 18 | /// Added persistence. 19 | /// 20 | /// . 21 | /// . 22 | public static IServiceCollection AddPersistence(this IServiceCollection services, IConfiguration configuration) 23 | { 24 | if (configuration.GetValue("UseInMemoryDatabase")) 25 | { 26 | services.AddDbContext(options => 27 | options.UseInMemoryDatabase("ApplicationDb")); 28 | } 29 | else 30 | { 31 | services.AddDbContext(options => 32 | options 33 | .UseSqlite( 34 | configuration.GetConnectionString("DefaultConnection"), 35 | b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)) 36 | .LogTo(Log.Logger.Information, LogLevel.Information)); 37 | } 38 | 39 | return services; 40 | } 41 | 42 | /// 43 | /// Added migrations database. 44 | /// 45 | /// . 46 | public static IServiceCollection AddMigrationsDatabase(this IServiceCollection services) 47 | { 48 | var serviceProvider = services.BuildServiceProvider(); 49 | var service = serviceProvider.GetRequiredService(); 50 | 51 | service.Database.Migrate(); 52 | return services; 53 | } 54 | 55 | /// 56 | /// Registers the unit of work given context as a service in the . 57 | /// 58 | /// The type of the db context. 59 | /// The to add services to. 60 | /// The same service collection so that multiple calls can be chained. 61 | /// 62 | /// This method only support one db context, if been called more than once, will throw exception. 63 | /// 64 | public static IServiceCollection AddUnitOfWork(this IServiceCollection services) 65 | where TContext : DbContext 66 | { 67 | services.AddScoped>(); 68 | services.AddScoped>(); 69 | services.AddScoped, UnitOfWork>(); 70 | 71 | return services; 72 | } 73 | 74 | /// 75 | /// Registers the unit of work given context as a service in the . 76 | /// 77 | /// The type of the db context. 78 | /// The type of the db context2. 79 | /// The to add services to. 80 | /// The same service collection so that multiple calls can be chained. 81 | /// 82 | /// This method only support one db context, if been called more than once, will throw exception. 83 | /// 84 | public static IServiceCollection AddUnitOfWork(this IServiceCollection services) 85 | where TContext1 : DbContext 86 | where TContext2 : DbContext 87 | { 88 | services.AddScoped, UnitOfWork>(); 89 | services.AddScoped, UnitOfWork>(); 90 | 91 | return services; 92 | } 93 | 94 | /// 95 | /// Registers the unit of work given context as a service in the . 96 | /// 97 | /// The type of the db context. 98 | /// The type of the db context2. 99 | /// The type of the db context3. 100 | /// The to add services to. 101 | /// The same service collection so that multiple calls can be chained. 102 | /// 103 | /// This method only support one db context, if been called more than once, will throw exception. 104 | /// 105 | public static IServiceCollection AddUnitOfWork( 106 | this IServiceCollection services) 107 | where TContext1 : DbContext 108 | where TContext2 : DbContext 109 | where TContext3 : DbContext 110 | { 111 | services.AddScoped, UnitOfWork>(); 112 | services.AddScoped, UnitOfWork>(); 113 | services.AddScoped, UnitOfWork>(); 114 | 115 | return services; 116 | } 117 | 118 | /// 119 | /// Registers the unit of work given context as a service in the . 120 | /// 121 | /// The type of the db context. 122 | /// The type of the db context2. 123 | /// The type of the db context3. 124 | /// The type of the db context4. 125 | /// The to add services to. 126 | /// The same service collection so that multiple calls can be chained. 127 | /// 128 | /// This method only support one db context, if been called more than once, will throw exception. 129 | /// 130 | public static IServiceCollection AddUnitOfWork( 131 | this IServiceCollection services) 132 | where TContext1 : DbContext 133 | where TContext2 : DbContext 134 | where TContext3 : DbContext 135 | where TContext4 : DbContext 136 | { 137 | services.AddScoped, UnitOfWork>(); 138 | services.AddScoped, UnitOfWork>(); 139 | services.AddScoped, UnitOfWork>(); 140 | services.AddScoped, UnitOfWork>(); 141 | 142 | return services; 143 | } 144 | 145 | /// 146 | /// Registers the custom repository as a service in the . 147 | /// 148 | /// The type of the entity. 149 | /// The type of the custom repository. 150 | /// The to add services to. 151 | /// The same service collection so that multiple calls can be chained. 152 | public static IServiceCollection AddCustomRepository(this IServiceCollection services) 153 | where TEntity : class 154 | where TRepository : class, IRepository 155 | { 156 | services.AddScoped, TRepository>(); 157 | 158 | return services; 159 | } 160 | } 161 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure.Persistence/Factories/ApplicationDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Persistence.Factories; 2 | 3 | using Contexts; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Design; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | /// 9 | public class ApplicationDbContextFactory : IDesignTimeDbContextFactory 10 | { 11 | /// 12 | public ApplicationDbContext CreateDbContext(string[] args) 13 | { 14 | var currentEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Development"; 15 | var configuration = new ConfigurationBuilder() 16 | .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../../UI/DesktopClearArchitecture.Client")) 17 | .AddJsonFile("appsettings.json") 18 | .AddJsonFile($"appsettings.{currentEnvironment}.json") 19 | .Build(); 20 | 21 | var optionsBuilder = new DbContextOptionsBuilder(); 22 | optionsBuilder.UseSqlite( 23 | configuration.GetConnectionString("DefaultConnection"), 24 | b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)); 25 | return new ApplicationDbContext(optionsBuilder.Options); 26 | } 27 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure.Persistence/Repositories/Repository.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace DesktopClearArchitecture.Infrastructure.Persistence.Repositories; 4 | 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using DesktopClearArchitecture.Domain.Abstractions.Repositories; 8 | using Extensions; 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.EntityFrameworkCore.ChangeTracking; 11 | using Microsoft.EntityFrameworkCore.Metadata; 12 | using Microsoft.EntityFrameworkCore.Query; 13 | 14 | /// 15 | /// Represents a default generic repository implements the interface. 16 | /// 17 | /// The type of the entity. 18 | public sealed class Repository : IRepository 19 | where TEntity : class 20 | { 21 | private readonly DbContext _dbContext; 22 | private readonly DbSet _dbSet; 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The database context. 28 | public Repository(DbContext dbContext) 29 | { 30 | _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); 31 | _dbSet = _dbContext.Set(); 32 | } 33 | 34 | /// 35 | public void ChangeEntityState(TEntity entity, EntityState state) 36 | { 37 | _dbContext.Entry(entity).State = state; 38 | } 39 | 40 | /// 41 | public void ChangeTable(string table) 42 | { 43 | if (_dbContext.Model.FindEntityType(typeof(TEntity)) is IConventionEntityType relational) 44 | relational.SetTableName(table); 45 | } 46 | 47 | /// 48 | public IQueryable GetAll(bool disableTracking = true) 49 | { 50 | return disableTracking 51 | ? _dbSet.AsNoTracking() 52 | : _dbSet; 53 | } 54 | 55 | /// 56 | public IQueryable GetAll( 57 | Expression> selector, 58 | bool disableTracking = true) 59 | { 60 | return disableTracking 61 | ? _dbSet.AsNoTracking().Select(selector) 62 | : _dbSet.Select(selector); 63 | } 64 | 65 | /// 66 | public IQueryable GetAll( 67 | Expression> selector, 68 | Expression>? predicate = null, 69 | bool disableTracking = true) 70 | { 71 | IQueryable query = _dbSet; 72 | 73 | if (disableTracking) 74 | query = query.AsNoTracking(); 75 | 76 | if (predicate is not null) 77 | query = query.Where(predicate); 78 | 79 | return query.Select(selector); 80 | } 81 | 82 | /// 83 | public IQueryable GetAll( 84 | Expression>? predicate = null, 85 | Func, IOrderedQueryable>? orderBy = null, 86 | Func, IIncludableQueryable>? include = null, 87 | bool disableTracking = true, 88 | bool ignoreQueryFilters = false) 89 | { 90 | IQueryable query = _dbSet; 91 | 92 | if (disableTracking) 93 | query = query.AsNoTracking(); 94 | 95 | if (include is not null) 96 | query = include(query); 97 | 98 | if (predicate is not null) 99 | query = query.Where(predicate); 100 | 101 | if (ignoreQueryFilters) 102 | query = query.IgnoreQueryFilters(); 103 | 104 | return orderBy is not null 105 | ? orderBy(query) 106 | : query; 107 | } 108 | 109 | /// 110 | public IQueryable GetAll( 111 | Expression> selector, 112 | Expression>? predicate = null, 113 | Func, IOrderedQueryable>? orderBy = null, 114 | Func, IIncludableQueryable>? include = null, 115 | bool disableTracking = true, 116 | bool ignoreQueryFilters = false) 117 | { 118 | IQueryable query = _dbSet; 119 | 120 | if (disableTracking) 121 | query = query.AsNoTracking(); 122 | 123 | if (include is not null) 124 | query = include(query); 125 | 126 | if (predicate is not null) 127 | query = query.Where(predicate); 128 | 129 | if (ignoreQueryFilters) 130 | query = query.IgnoreQueryFilters(); 131 | 132 | return orderBy != null 133 | ? orderBy(query).Select(selector) 134 | : query.Select(selector); 135 | } 136 | 137 | /// 138 | public async Task> GetAllAsync(bool disableTracking = true) 139 | { 140 | return disableTracking 141 | ? await _dbSet.AsNoTracking().ToListAsync() 142 | : await _dbSet.ToListAsync(); 143 | } 144 | 145 | /// 146 | public async Task> GetAllAsync( 147 | Expression> selector, 148 | bool disableTracking = true) 149 | { 150 | return disableTracking 151 | ? await _dbSet.AsNoTracking().Select(selector).ToListAsync() 152 | : await _dbSet.Select(selector).ToListAsync(); 153 | } 154 | 155 | /// 156 | public async Task> GetAllAsync( 157 | Expression>? predicate = null, 158 | Func, IOrderedQueryable>? orderBy = null, 159 | Func, IIncludableQueryable>? include = null, 160 | bool disableTracking = true, 161 | bool ignoreQueryFilters = false) 162 | { 163 | IQueryable query = _dbSet; 164 | 165 | if (disableTracking) 166 | query = query.AsNoTracking(); 167 | 168 | if (include is not null) 169 | query = include(query); 170 | 171 | if (predicate is not null) 172 | query = query.Where(predicate); 173 | 174 | if (ignoreQueryFilters) 175 | query = query.IgnoreQueryFilters(); 176 | 177 | if (orderBy is not null) 178 | return await orderBy(query).ToListAsync(); 179 | 180 | return await query.ToListAsync(); 181 | } 182 | 183 | /// 184 | public async Task> GetAllAsync( 185 | Expression> selector, 186 | Expression>? predicate = null, 187 | Func, IOrderedQueryable>? orderBy = null, 188 | Func, IIncludableQueryable>? include = null, 189 | bool disableTracking = true, 190 | bool ignoreQueryFilters = false) 191 | { 192 | IQueryable query = _dbSet; 193 | 194 | if (disableTracking) 195 | query = query.AsNoTracking(); 196 | 197 | if (include is not null) 198 | query = include(query); 199 | 200 | if (predicate is not null) 201 | query = query.Where(predicate); 202 | 203 | if (ignoreQueryFilters) 204 | query = query.IgnoreQueryFilters(); 205 | 206 | return orderBy is not null 207 | ? await orderBy(query).Select(selector).ToListAsync() 208 | : await query.Select(selector).ToListAsync(); 209 | } 210 | 211 | /// 212 | public IPagedList GetPagedList( 213 | Expression>? predicate = null, 214 | Func, IOrderedQueryable>? orderBy = null, 215 | Func, IIncludableQueryable>? include = null, 216 | int pageIndex = 0, 217 | int pageSize = 20, 218 | bool disableTracking = true, 219 | bool ignoreQueryFilters = false) 220 | { 221 | IQueryable query = _dbSet; 222 | 223 | if (disableTracking) 224 | query = query.AsNoTracking(); 225 | 226 | if (include is not null) 227 | query = include(query); 228 | 229 | if (predicate is not null) 230 | query = query.Where(predicate); 231 | 232 | if (ignoreQueryFilters) 233 | query = query.IgnoreQueryFilters(); 234 | 235 | return orderBy is not null 236 | ? orderBy(query).ToPagedList(pageIndex, pageSize) 237 | : query.ToPagedList(pageIndex, pageSize); 238 | } 239 | 240 | /// 241 | public Task> GetPagedListAsync( 242 | Expression>? predicate = null, 243 | Func, IOrderedQueryable>? orderBy = null, 244 | Func, IIncludableQueryable>? include = null, 245 | int pageIndex = 0, 246 | int pageSize = 20, 247 | bool disableTracking = true, 248 | CancellationToken cancellationToken = default, 249 | bool ignoreQueryFilters = false) 250 | { 251 | IQueryable query = _dbSet; 252 | 253 | if (disableTracking) 254 | query = query.AsNoTracking(); 255 | 256 | if (include is not null) 257 | query = include(query); 258 | 259 | if (predicate is not null) 260 | query = query.Where(predicate); 261 | 262 | if (ignoreQueryFilters) 263 | query = query.IgnoreQueryFilters(); 264 | 265 | return orderBy is not null 266 | ? orderBy(query).ToPagedListAsync(pageIndex, pageSize, 0, cancellationToken) 267 | : query.ToPagedListAsync(pageIndex, pageSize, 0, cancellationToken); 268 | } 269 | 270 | /// 271 | public IPagedList GetPagedList( 272 | Expression> selector, 273 | Expression>? predicate = null, 274 | Func, IOrderedQueryable>? orderBy = null, 275 | Func, IIncludableQueryable>? include = null, 276 | int pageIndex = 0, 277 | int pageSize = 20, 278 | bool disableTracking = true, 279 | bool ignoreQueryFilters = false) 280 | where TResult : class 281 | { 282 | IQueryable query = _dbSet; 283 | 284 | if (disableTracking) 285 | query = query.AsNoTracking(); 286 | 287 | if (include is not null) 288 | query = include(query); 289 | 290 | if (predicate is not null) 291 | query = query.Where(predicate); 292 | 293 | if (ignoreQueryFilters) 294 | query = query.IgnoreQueryFilters(); 295 | 296 | return orderBy is not null 297 | ? orderBy(query).Select(selector).ToPagedList(pageIndex, pageSize) 298 | : query.Select(selector).ToPagedList(pageIndex, pageSize); 299 | } 300 | 301 | /// 302 | public Task> GetPagedListAsync( 303 | Expression> selector, 304 | Expression>? predicate = null, 305 | Func, IOrderedQueryable>? orderBy = null, 306 | Func, IIncludableQueryable>? include = null, 307 | int pageIndex = 0, 308 | int pageSize = 20, 309 | bool disableTracking = true, 310 | CancellationToken cancellationToken = default, 311 | bool ignoreQueryFilters = false) 312 | where TResult : class 313 | { 314 | if (selector == null) 315 | throw new ArgumentNullException(nameof(selector)); 316 | 317 | IQueryable query = _dbSet; 318 | 319 | if (disableTracking) 320 | query = query.AsNoTracking(); 321 | 322 | if (include != null) 323 | query = include(query); 324 | 325 | if (predicate != null) 326 | query = query.Where(predicate); 327 | 328 | if (ignoreQueryFilters) 329 | query = query.IgnoreQueryFilters(); 330 | 331 | if (orderBy != null) 332 | return orderBy(query).Select(selector).ToPagedListAsync(pageIndex, pageSize, 0, cancellationToken); 333 | 334 | return query.Select(selector).ToPagedListAsync(pageIndex, pageSize, 0, cancellationToken); 335 | } 336 | 337 | /// 338 | public TEntity? GetFirstOrDefault( 339 | Expression>? predicate = null, 340 | Func, IOrderedQueryable>? orderBy = null, 341 | Func, IIncludableQueryable>? include = null, 342 | bool disableTracking = true, 343 | bool ignoreQueryFilters = false) 344 | { 345 | IQueryable query = _dbSet; 346 | 347 | if (disableTracking) 348 | query = query.AsNoTracking(); 349 | 350 | if (include is not null) 351 | query = include(query); 352 | 353 | if (predicate is not null) 354 | query = query.Where(predicate); 355 | 356 | if (ignoreQueryFilters) 357 | query = query.IgnoreQueryFilters(); 358 | 359 | return orderBy is not null 360 | ? orderBy(query).FirstOrDefault() 361 | : query.FirstOrDefault(); 362 | } 363 | 364 | /// 365 | public async Task GetFirstOrDefaultAsync( 366 | Expression>? predicate = null, 367 | Func, IOrderedQueryable>? orderBy = null, 368 | Func, IIncludableQueryable>? include = null, 369 | bool disableTracking = true, 370 | bool ignoreQueryFilters = false) 371 | { 372 | IQueryable query = _dbSet; 373 | 374 | if (disableTracking) 375 | query = query.AsNoTracking(); 376 | 377 | if (include is not null) 378 | query = include(query); 379 | 380 | if (predicate is not null) 381 | query = query.Where(predicate); 382 | 383 | if (ignoreQueryFilters) 384 | query = query.IgnoreQueryFilters(); 385 | 386 | return orderBy is not null 387 | ? await orderBy(query).FirstOrDefaultAsync() 388 | : await query.FirstOrDefaultAsync(); 389 | } 390 | 391 | /// 392 | public TResult? GetFirstOrDefault( 393 | Expression> selector, 394 | Expression>? predicate = null, 395 | Func, IOrderedQueryable>? orderBy = null, 396 | Func, IIncludableQueryable>? include = null, 397 | bool disableTracking = true, 398 | bool ignoreQueryFilters = false) 399 | { 400 | IQueryable query = _dbSet; 401 | 402 | if (disableTracking) 403 | query = query.AsNoTracking(); 404 | 405 | if (include is not null) 406 | query = include(query); 407 | 408 | if (predicate is not null) 409 | query = query.Where(predicate); 410 | 411 | if (ignoreQueryFilters) 412 | query = query.IgnoreQueryFilters(); 413 | 414 | return orderBy is not null 415 | ? orderBy(query).Select(selector).FirstOrDefault() 416 | : query.Select(selector).FirstOrDefault(); 417 | } 418 | 419 | /// 420 | public async Task GetFirstOrDefaultAsync( 421 | Expression> selector, 422 | Expression>? predicate = null, 423 | Func, IOrderedQueryable>? orderBy = null, 424 | Func, IIncludableQueryable>? include = null, 425 | bool disableTracking = true, 426 | bool ignoreQueryFilters = false) 427 | { 428 | IQueryable query = _dbSet; 429 | 430 | if (disableTracking) 431 | query = query.AsNoTracking(); 432 | 433 | if (include is not null) 434 | query = include(query); 435 | 436 | if (predicate is not null) 437 | query = query.Where(predicate); 438 | 439 | if (ignoreQueryFilters) 440 | query = query.IgnoreQueryFilters(); 441 | 442 | return orderBy is not null 443 | ? await orderBy(query).Select(selector).FirstOrDefaultAsync() 444 | : await query.Select(selector).FirstOrDefaultAsync(); 445 | } 446 | 447 | /// 448 | public IQueryable FromSql(string sql, params object[] parameters) 449 | { 450 | return _dbSet.FromSqlRaw(sql, parameters); 451 | } 452 | 453 | /// 454 | public TEntity? Find(params object[] keyValues) 455 | { 456 | return _dbSet.Find(keyValues); 457 | } 458 | 459 | /// 460 | public ValueTask FindAsync(params object[] keyValues) 461 | { 462 | return _dbSet.FindAsync(keyValues)!; 463 | } 464 | 465 | /// 466 | public ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) 467 | { 468 | return _dbSet.FindAsync(keyValues, cancellationToken)!; 469 | } 470 | 471 | /// 472 | public int Count(Expression>? predicate = null) 473 | { 474 | return predicate is null 475 | ? _dbSet.Count() 476 | : _dbSet.Count(predicate); 477 | } 478 | 479 | /// 480 | public async Task CountAsync( 481 | Expression>? predicate = null, 482 | CancellationToken cancellationToken = default) 483 | { 484 | return predicate is null 485 | ? await _dbSet.CountAsync(cancellationToken) 486 | : await _dbSet.CountAsync(predicate, cancellationToken); 487 | } 488 | 489 | /// 490 | public long LongCount(Expression>? predicate = null) 491 | { 492 | return predicate is null 493 | ? _dbSet.LongCount() 494 | : _dbSet.LongCount(predicate); 495 | } 496 | 497 | /// 498 | public async Task LongCountAsync( 499 | Expression>? predicate = null, 500 | CancellationToken cancellationToken = default) 501 | { 502 | return predicate is null 503 | ? await _dbSet.LongCountAsync(cancellationToken) 504 | : await _dbSet.LongCountAsync(predicate, cancellationToken); 505 | } 506 | 507 | /// 508 | public bool Exists(Expression>? predicate = null) 509 | { 510 | return predicate is null 511 | ? _dbSet.Any() 512 | : _dbSet.Any(predicate); 513 | } 514 | 515 | /// 516 | public async Task ExistsAsync( 517 | Expression>? selector = null, 518 | CancellationToken cancellationToken = default) 519 | { 520 | return selector is null 521 | ? await _dbSet.AnyAsync(cancellationToken) 522 | : await _dbSet.AnyAsync(selector, cancellationToken); 523 | } 524 | 525 | /// 526 | public T? Max( 527 | Expression> selector, 528 | Expression>? predicate = null) 529 | { 530 | return predicate is null 531 | ? _dbSet.Max(selector) 532 | : _dbSet.Where(predicate).Max(selector); 533 | } 534 | 535 | /// 536 | public Task MaxAsync( 537 | Expression> selector, 538 | Expression>? predicate = null, 539 | CancellationToken cancellationToken = default) 540 | { 541 | return predicate is null 542 | ? _dbSet.MaxAsync(selector, cancellationToken) 543 | : _dbSet.Where(predicate).MaxAsync(selector, cancellationToken); 544 | } 545 | 546 | /// 547 | public T? Min( 548 | Expression> selector, 549 | Expression>? predicate = null) 550 | { 551 | return predicate is null 552 | ? _dbSet.Min(selector) 553 | : _dbSet.Where(predicate).Min(selector); 554 | } 555 | 556 | /// 557 | public Task MinAsync( 558 | Expression> selector, 559 | Expression>? predicate = null, 560 | CancellationToken cancellationToken = default) 561 | { 562 | return predicate is null 563 | ? _dbSet.MinAsync(selector, cancellationToken) 564 | : _dbSet.Where(predicate).MinAsync(selector, cancellationToken); 565 | } 566 | 567 | /// 568 | public decimal Average( 569 | Expression> selector, 570 | Expression>? predicate = null) 571 | { 572 | return predicate is null 573 | ? _dbSet.Average(selector) 574 | : _dbSet.Where(predicate).Average(selector); 575 | } 576 | 577 | /// 578 | public Task AverageAsync( 579 | Expression> selector, 580 | Expression>? predicate = null, 581 | CancellationToken cancellationToken = default) 582 | { 583 | return predicate is null 584 | ? _dbSet.AverageAsync(selector, cancellationToken) 585 | : _dbSet.Where(predicate).AverageAsync(selector, cancellationToken); 586 | } 587 | 588 | /// 589 | public decimal Sum( 590 | Expression> selector, 591 | Expression>? predicate = null) 592 | { 593 | return predicate is null 594 | ? _dbSet.Sum(selector) 595 | : _dbSet.Where(predicate).Sum(selector); 596 | } 597 | 598 | /// 599 | public Task SumAsync( 600 | Expression> selector, 601 | Expression>? predicate = null, 602 | CancellationToken cancellationToken = default) 603 | { 604 | return predicate is null 605 | ? _dbSet.SumAsync(selector, cancellationToken) 606 | : _dbSet.Where(predicate).SumAsync(selector, cancellationToken); 607 | } 608 | 609 | /// 610 | public TEntity Insert(TEntity entity) 611 | { 612 | return _dbSet.Add(entity).Entity; 613 | } 614 | 615 | /// 616 | public void Insert(params TEntity[] entities) 617 | { 618 | _dbSet.AddRange(entities); 619 | } 620 | 621 | /// 622 | public void Insert(IEnumerable entities) 623 | { 624 | _dbSet.AddRange(entities); 625 | } 626 | 627 | /// 628 | public ValueTask> InsertAsync(TEntity entity, CancellationToken cancellationToken = default) 629 | { 630 | return _dbSet.AddAsync(entity, cancellationToken); 631 | } 632 | 633 | /// 634 | public Task InsertAsync(params TEntity[] entities) 635 | { 636 | return _dbSet.AddRangeAsync(entities); 637 | } 638 | 639 | /// 640 | public Task InsertAsync(IEnumerable entities, CancellationToken cancellationToken = default) 641 | { 642 | return _dbSet.AddRangeAsync(entities, cancellationToken); 643 | } 644 | 645 | /// 646 | public void Update(TEntity entity) 647 | { 648 | _dbSet.Update(entity); 649 | } 650 | 651 | /// 652 | public void UpdateAsync(TEntity entity) 653 | { 654 | _dbSet.Update(entity); 655 | } 656 | 657 | /// 658 | public void Update(params TEntity[] entities) 659 | { 660 | _dbSet.UpdateRange(entities); 661 | } 662 | 663 | /// 664 | public void Update(IEnumerable entities) 665 | { 666 | _dbSet.UpdateRange(entities); 667 | } 668 | 669 | /// 670 | public void Delete(TEntity entity) 671 | { 672 | _dbSet.Remove(entity); 673 | } 674 | 675 | /// 676 | public void Delete(object id) 677 | { 678 | var typeInfo = typeof(TEntity).GetTypeInfo(); 679 | var key = _dbContext.Model.FindEntityType(typeInfo)? 680 | .FindPrimaryKey()?.Properties 681 | .FirstOrDefault(); 682 | if (key is null) 683 | return; 684 | 685 | var property = typeInfo.GetProperty(key.Name); 686 | if (property != null) 687 | { 688 | var entity = Activator.CreateInstance(); 689 | property.SetValue(entity, id); 690 | _dbContext.Entry(entity).State = EntityState.Deleted; 691 | } 692 | else 693 | { 694 | var entity = _dbSet.Find(id); 695 | if (entity != null) 696 | Delete(entity); 697 | } 698 | } 699 | 700 | /// 701 | public void Delete(params TEntity[] entities) 702 | { 703 | _dbSet.RemoveRange(entities); 704 | } 705 | 706 | /// 707 | public void Delete(IEnumerable entities) 708 | { 709 | _dbSet.RemoveRange(entities); 710 | } 711 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure.Persistence/Repositories/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace DesktopClearArchitecture.Infrastructure.Persistence.Repositories; 4 | 5 | using DesktopClearArchitecture.Domain.Abstractions.Repositories; 6 | using Domain.Common.Result; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.EntityFrameworkCore.ChangeTracking; 9 | using Microsoft.EntityFrameworkCore.Infrastructure; 10 | using Microsoft.EntityFrameworkCore.Storage; 11 | 12 | /// 13 | /// Represents the default implementation of the and interface. 14 | /// 15 | /// The type of the db context. 16 | public sealed class UnitOfWork : IRepositoryFactory, IUnitOfWork 17 | where TContext : DbContext 18 | { 19 | private bool _disposed; 20 | private Dictionary? _repositories; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The context. 26 | public UnitOfWork(TContext context) 27 | { 28 | DbContext = context ?? throw new ArgumentNullException(nameof(context)); 29 | LastSaveChangesResult = new SaveChangesResult(); 30 | } 31 | 32 | /// 33 | /// Finalizes an instance of the class. 34 | /// 35 | ~UnitOfWork() => Dispose(false); 36 | 37 | /// 38 | public TContext DbContext { get; } 39 | 40 | /// 41 | public SaveChangesResult LastSaveChangesResult { get; } 42 | 43 | /// 44 | public Task BeginTransactionAsync(bool useIfExists = false) 45 | { 46 | var transaction = DbContext.Database.CurrentTransaction; 47 | if (transaction == null) 48 | return DbContext.Database.BeginTransactionAsync(); 49 | 50 | return useIfExists ? Task.FromResult(transaction) : DbContext.Database.BeginTransactionAsync(); 51 | } 52 | 53 | /// 54 | public IDbContextTransaction BeginTransaction(bool useIfExists = false) 55 | { 56 | var transaction = DbContext.Database.CurrentTransaction; 57 | if (transaction == null) 58 | return DbContext.Database.BeginTransaction(); 59 | 60 | return useIfExists ? transaction : DbContext.Database.BeginTransaction(); 61 | } 62 | 63 | /// 64 | public void SetAutoDetectChanges(bool value) => DbContext.ChangeTracker.AutoDetectChangesEnabled = value; 65 | 66 | /// 67 | public IRepository GetRepository(bool hasCustomRepository = false) 68 | where TEntity : class 69 | { 70 | _repositories ??= new Dictionary(); 71 | 72 | if (hasCustomRepository) 73 | { 74 | var customRepo = DbContext.GetService>(); 75 | if (customRepo != null) 76 | return customRepo; 77 | } 78 | 79 | var type = typeof(TEntity); 80 | if (!_repositories.ContainsKey(type)) 81 | _repositories[type] = new Repository(DbContext); 82 | 83 | return (IRepository)_repositories[type]; 84 | } 85 | 86 | /// 87 | public int ExecuteSqlCommand(string sql, params object[] parameters) => 88 | DbContext.Database.ExecuteSqlRaw(sql, parameters); 89 | 90 | /// 91 | public Task ExecuteSqlCommandAsync(string sql, params object[] parameters) => 92 | DbContext.Database.ExecuteSqlRawAsync(sql, parameters); 93 | 94 | /// 95 | public IQueryable FromSqlRaw(string sql, params object[] parameters) 96 | where TEntity : class 97 | => DbContext.Set().FromSqlRaw(sql, parameters); 98 | 99 | /// 100 | public int SaveChanges() 101 | { 102 | try 103 | { 104 | return DbContext.SaveChanges(); 105 | } 106 | catch (Exception exception) 107 | { 108 | LastSaveChangesResult.Exception = exception; 109 | return 0; 110 | } 111 | } 112 | 113 | /// 114 | public async Task SaveChangesAsync() 115 | { 116 | try 117 | { 118 | return await DbContext.SaveChangesAsync(); 119 | } 120 | catch (Exception exception) 121 | { 122 | LastSaveChangesResult.Exception = exception; 123 | return 0; 124 | } 125 | } 126 | 127 | /// 128 | public async Task SaveChangesAsync(params IUnitOfWork[] unitOfWorks) 129 | { 130 | var count = 0; 131 | foreach (var unitOfWork in unitOfWorks) 132 | count += await unitOfWork.SaveChangesAsync(); 133 | 134 | count += await SaveChangesAsync(); 135 | return count; 136 | } 137 | 138 | /// 139 | public void Dispose() 140 | { 141 | Dispose(true); 142 | GC.SuppressFinalize(this); 143 | } 144 | 145 | /// 146 | public void TrackGraph(object rootEntity, Action callback) => 147 | DbContext.ChangeTracker.TrackGraph(rootEntity, callback); 148 | 149 | private void Dispose(bool disposing) 150 | { 151 | if (!_disposed) 152 | { 153 | if (disposing) 154 | { 155 | _repositories?.Clear(); 156 | DbContext.Dispose(); 157 | } 158 | } 159 | 160 | _disposed = true; 161 | } 162 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure/DesktopClearArchitecture.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure/Models/GameList.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Models; 2 | 3 | using System.Collections.Generic; 4 | using System.Runtime.Serialization; 5 | using DesktopClearArchitecture.Domain.Models; 6 | 7 | /// 8 | /// App list. 9 | /// 10 | public class GameList 11 | { 12 | /// 13 | /// App. 14 | /// 15 | [DataMember(Name = "apps")] 16 | public List Games { get; set; } 17 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure/Models/Root.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Models; 2 | 3 | using System.Runtime.Serialization; 4 | 5 | /// 6 | /// Base class for deserialization. 7 | /// 8 | public class Root 9 | { 10 | /// 11 | /// App list. 12 | /// 13 | [DataMember(Name = "applist")] 14 | public GameList GameList { get; set; } 15 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure/Services/GameSearcher.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Services; 2 | 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using DesktopClearArchitecture.Domain.Models; 7 | using Domain.Abstractions; 8 | using Models; 9 | using Scrutor.AspNetCore; 10 | using LazyCache; 11 | 12 | /// 13 | public class GameSearcher : IGameSearcher, ISingletonLifetime 14 | { 15 | private readonly HttpClient _httpClient; 16 | private readonly IAppCache _cache; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// . 22 | /// . 23 | public GameSearcher(HttpClient httpClient, IAppCache cache) 24 | { 25 | _httpClient = httpClient; 26 | _cache = cache; 27 | } 28 | 29 | /// 30 | public async Task> GetGames() => 31 | await _cache.GetOrAdd("GetGames", GetGamesList, TimeSpan.FromSeconds(10)); 32 | 33 | private async Task> GetGamesList() 34 | { 35 | var response = await _httpClient.GetAsync("https://api.steampowered.com/ISteamApps/GetAppList/v0002/?format=json"); 36 | response.EnsureSuccessStatusCode(); 37 | 38 | var stream = await response.Content.ReadAsStreamAsync(); 39 | return Utf8Json.JsonSerializer.Deserialize(stream).GameList.Games; 40 | } 41 | } -------------------------------------------------------------------------------- /src/Infrastructure/DesktopClearArchitecture.Infrastructure/Services/MusicPlayer.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Infrastructure.Services; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using DesktopClearArchitecture.Domain.Models; 7 | using Domain.Abstractions; 8 | using Scrutor.AspNetCore; 9 | 10 | /// 11 | public class MusicPlayer : IMusicPlayer, ISingletonLifetime 12 | { 13 | private static readonly string[] NameSongs = 14 | { 15 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 16 | }; 17 | 18 | private readonly Random _random = new(); 19 | 20 | /// 21 | public IEnumerable GetSongs() 22 | { 23 | return Enumerable 24 | .Range(0, 10) 25 | .Select(_ => new Song 26 | { 27 | Name = NameSongs[_random.Next(NameSongs.Length)], 28 | Duration = _random.Next(0, 360) 29 | }); 30 | } 31 | } -------------------------------------------------------------------------------- /src/UI/DesktopClearArchitecture.Client/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/UI/DesktopClearArchitecture.Client/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Client; 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Windows; 7 | using System.Windows.Threading; 8 | using Application.Extensions; 9 | using DesktopClearArchitecture.UI.Dialogs.Authorization.ViewModels; 10 | using DesktopClearArchitecture.UI.Dialogs.Authorization.Views; 11 | using Infrastructure.Persistence.Contexts; 12 | using Infrastructure.Persistence.Extensions; 13 | using Microsoft.Extensions.Configuration; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Prism.Ioc; 16 | using Prism.Modularity; 17 | using Prism.Unity; 18 | using Serilog; 19 | using UI.Modules.Games; 20 | using UI.Modules.Home; 21 | using UI.Modules.Music; 22 | using UI.Modules.Products; 23 | using UI.Modules.Settings; 24 | using Unity; 25 | using Unity.Microsoft.DependencyInjection; 26 | using Views; 27 | 28 | /// 29 | public partial class App 30 | { 31 | private ILogger _logger = null!; 32 | private IConfigurationRoot _configuration = null!; 33 | 34 | /// 35 | public App() 36 | { 37 | Current.DispatcherUnhandledException += CurrentOnDispatcherUnhandledException; 38 | } 39 | 40 | /// 41 | protected override void OnStartup(StartupEventArgs e) 42 | { 43 | base.OnStartup(e); 44 | 45 | _logger = Log.ForContext(); 46 | _logger.Information("Start application. Start args count: {Count}", e.Args.Length); 47 | } 48 | 49 | /// 50 | protected override void OnExit(ExitEventArgs e) 51 | { 52 | _logger.Information("Exit application. Exit code: {Code}", e.ApplicationExitCode); 53 | 54 | base.OnExit(e); 55 | } 56 | 57 | /// 58 | protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) 59 | { 60 | moduleCatalog.AddModule(); 61 | moduleCatalog.AddModule(); 62 | moduleCatalog.AddModule(); 63 | moduleCatalog.AddModule(); 64 | moduleCatalog.AddModule(); 65 | } 66 | 67 | /// 68 | protected override void RegisterTypes(IContainerRegistry containerRegistry) 69 | { 70 | containerRegistry.RegisterDialog("AuthorizationDialog"); 71 | } 72 | 73 | /// 74 | protected override IContainerExtension CreateContainerExtension() 75 | { 76 | GetConfiguration(); 77 | 78 | var serviceCollection = new ServiceCollection(); 79 | serviceCollection 80 | .AddLazyCache() 81 | .AddLogging(loggingBuilder => 82 | loggingBuilder.AddSerilog(dispose: true)) 83 | .AddMapster() 84 | .AddHttpClient() 85 | .AddPersistence(_configuration) 86 | .AddUnitOfWork() 87 | .AddAdvancedDependencyInjection(); 88 | 89 | var container = new UnityContainer(); 90 | container.BuildServiceProvider(serviceCollection); 91 | 92 | return new UnityContainerExtension(container); 93 | } 94 | 95 | /// 96 | protected override Window CreateShell() 97 | { 98 | ConfigurationLogging(); 99 | 100 | return Container.Resolve(); 101 | } 102 | 103 | private static void ShowUnhandledException(DispatcherUnhandledExceptionEventArgs e) 104 | { 105 | e.Handled = true; 106 | 107 | var error = e.Exception.Message + 108 | (e.Exception.InnerException != null ? "\n" + e.Exception.InnerException.Message : null); 109 | var errorMessage = 110 | "An application error occurred.\nPlease check whether your data is correct and repeat the action. " + 111 | "If this error occurs again there seems to be a more serious malfunction in the application, " + 112 | $"and you better close it.\n\nError: {error}\n\n" + "Do you want to continue?\n" + 113 | "(If you click 'Yes' you will continue with your work, if you click 'No' the application will close)."; 114 | 115 | if (MessageBox.Show( 116 | errorMessage, 117 | "Application Error.", 118 | MessageBoxButton.YesNo, 119 | MessageBoxImage.Error) != MessageBoxResult.No) 120 | return; 121 | if (MessageBox.Show( 122 | "WARNING: The application will close. Any changes will not be saved!\n\n" + 123 | "Do you really want to close it?", 124 | "Close the application!", 125 | MessageBoxButton.YesNo, 126 | MessageBoxImage.Warning) == MessageBoxResult.Yes) 127 | { 128 | Current.Shutdown(); 129 | } 130 | } 131 | 132 | private void GetConfiguration() 133 | { 134 | var currentEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Development"; 135 | _configuration = new ConfigurationBuilder() 136 | .SetBasePath(Directory.GetCurrentDirectory()) 137 | .AddJsonFile("appsettings.json") 138 | .AddJsonFile($"appsettings.{currentEnvironment}.json") 139 | .Build(); 140 | } 141 | 142 | private void ConfigurationLogging() 143 | { 144 | Log.Logger = new LoggerConfiguration() 145 | .ReadFrom.Configuration(_configuration) 146 | .CreateLogger(); 147 | } 148 | 149 | private void CurrentOnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) 150 | { 151 | _logger.Warning(e.Exception.Demystify(), "Application Error"); 152 | 153 | if (Debugger.IsAttached) 154 | e.Handled = false; 155 | else 156 | ShowUnhandledException(e); 157 | } 158 | } -------------------------------------------------------------------------------- /src/UI/DesktopClearArchitecture.Client/DesktopClearArchitecture.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | WinExe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | PreserveNewest 47 | 48 | 49 | 50 | PreserveNewest 51 | 52 | 53 | 54 | PreserveNewest 55 | 56 | 57 | 58 | PreserveNewest 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/UI/DesktopClearArchitecture.Client/Models/DataNavigationView.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Client.Models; 2 | 3 | /// 4 | /// Data navigation view. 5 | /// 6 | public readonly struct DataNavigationView 7 | { 8 | /// 9 | /// Name content element. 10 | /// 11 | public string NameContentElement { get; init; } 12 | 13 | /// 14 | /// Name control. 15 | /// 16 | public string NameControl { get; init; } 17 | 18 | /// 19 | public override string ToString() => NameContentElement; 20 | } -------------------------------------------------------------------------------- /src/UI/DesktopClearArchitecture.Client/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace DesktopClearArchitecture.Client.ViewModels 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reactive.Linq; 7 | using DesktopClearArchitecture.Shared.ViewModels; 8 | using DesktopClearArchitecture.UI.Modules.Games.Views; 9 | using DesktopClearArchitecture.UI.Modules.Home.Views; 10 | using DesktopClearArchitecture.UI.Modules.Music.Views; 11 | using DesktopClearArchitecture.UI.Modules.Products.Views; 12 | using DesktopClearArchitecture.UI.Modules.Settings.Views; 13 | using Models; 14 | using ModernWpf.Controls; 15 | using Prism.Regions; 16 | using Reactive.Bindings; 17 | using Shared.Constants; 18 | using Views; 19 | 20 | /// 21 | /// View model for . 22 | /// 23 | public class MainWindowViewModel : NavigationViewModelBase 24 | { 25 | private static readonly NavigationViewItemBase[] NavigationViewItems = 26 | { 27 | new NavigationViewItem 28 | { 29 | Content = "Home", 30 | Icon = new SymbolIcon(Symbol.Home), 31 | Tag = nameof(HomeControl) 32 | }, 33 | new NavigationViewItemSeparator(), 34 | new NavigationViewItem 35 | { 36 | Content = "Products", 37 | Icon = new SymbolIcon(Symbol.AllApps), 38 | Tag = nameof(ProductsControl) 39 | }, 40 | new NavigationViewItem 41 | { 42 | Content = "Music", 43 | Icon = new SymbolIcon(Symbol.MusicInfo), 44 | Tag = nameof(MusicControl) 45 | }, 46 | new NavigationViewItem 47 | { 48 | Content = "Games", 49 | Icon = new SymbolIcon(Symbol.AllApps), 50 | Tag = nameof(GamesControl), 51 | MenuItemsSource = new List 52 | { 53 | new NavigationViewItem 54 | { 55 | Content = "Action", 56 | Icon = new SymbolIcon(Symbol.Filter), 57 | Tag = nameof(GamesControl) 58 | }, 59 | new NavigationViewItem 60 | { 61 | Content = "RPG", 62 | Icon = new SymbolIcon(Symbol.Account), 63 | Tag = nameof(GamesControl) 64 | } 65 | } 66 | } 67 | }; 68 | 69 | private readonly IRegionManager _regionManager; 70 | 71 | /// 72 | public MainWindowViewModel(IRegionManager regionManager) 73 | { 74 | _regionManager = regionManager; 75 | 76 | var navigationViewItems = GetNavigationViewItems(NavigationViewItems) 77 | .ToList(); 78 | var dataNavigationViewItems = navigationViewItems 79 | .Select(z => new DataNavigationView 80 | { 81 | NameContentElement = z.Content.ToString(), 82 | NameControl = z.Tag.ToString() 83 | }) 84 | .ToList(); 85 | 86 | AutoSuggestBoxItems = AutoSuggestBoxSearchText 87 | .Skip(1) 88 | .Throttle(TimeSpan.FromMilliseconds(500)) 89 | .Select(term => term?.Trim()) 90 | .DistinctUntilChanged() 91 | .Select(x => dataNavigationViewItems 92 | .Where(y => y.NameContentElement.Contains(x, StringComparison.OrdinalIgnoreCase)) 93 | .ToArray()) 94 | .ToReadOnlyReactivePropertySlim(); 95 | 96 | NavigationSelectedItem = NavigationMenuQuerySubmitted 97 | .DistinctUntilChanged() 98 | .Select(args => navigationViewItems.FirstOrDefault(x => (string)x.Content == args.QueryText)) 99 | .ToReactiveProperty(NavigationViewItems.FirstOrDefault()); 100 | 101 | NavigationMenuItemInvoked 102 | .DistinctUntilChanged() 103 | .Subscribe(args => 104 | MenuRequestNavigate(args.InvokedItemContainer.Tag.ToString(), args.IsSettingsInvoked)); 105 | 106 | NavigationMenuQuerySubmitted 107 | .DistinctUntilChanged() 108 | .Subscribe(args => 109 | MenuRequestNavigate(((DataNavigationView)args.ChosenSuggestion).NameControl)); 110 | 111 | CreateNavigationMenu 112 | .Subscribe(_ => 113 | { 114 | NavigationMenuItems.Value = NavigationViewItems 115 | .OrderBy(_ => Guid.NewGuid()) 116 | .ToArray(); 117 | }); 118 | } 119 | 120 | /// 121 | /// AutoSuggestBox search text. 122 | /// 123 | public ReactivePropertySlim AutoSuggestBoxSearchText { get; } = new(string.Empty); 124 | 125 | /// 126 | /// AutoSuggestBox items. 127 | /// 128 | public ReadOnlyReactivePropertySlim AutoSuggestBoxItems { get; } 129 | 130 | /// 131 | /// Navigation menu items. 132 | /// 133 | public ReactivePropertySlim NavigationMenuItems { get; } = new(NavigationViewItems); 134 | 135 | /// 136 | /// Navigation selected item. 137 | /// 138 | public ReactiveProperty NavigationSelectedItem { get; } 139 | 140 | /// 141 | /// Navigation menu item invoked. 142 | /// 143 | public ReactiveCommand NavigationMenuItemInvoked { get; } = new(); 144 | 145 | /// 146 | /// Navigation menu query submitted. 147 | /// 148 | public ReactiveCommand NavigationMenuQuerySubmitted { get; } = new(); 149 | 150 | /// 151 | /// Create navigation menu. 152 | /// 153 | public ReactiveCommand CreateNavigationMenu { get; } = new(); 154 | 155 | private static IEnumerable GetNavigationViewItems( 156 | IEnumerable items) 157 | { 158 | if (items == null) 159 | yield break; 160 | 161 | foreach (var item in items.OfType()) 162 | { 163 | yield return item; 164 | 165 | foreach (var subItem in GetNavigationViewItems( 166 | (IEnumerable)item.MenuItemsSource)) 167 | yield return subItem; 168 | } 169 | } 170 | 171 | private void MenuRequestNavigate(string nameControl, bool isSettings = false) => 172 | _regionManager.RequestNavigate(RegionNames.MainContent, isSettings ? nameof(SettingsControl) : nameControl); 173 | } 174 | } -------------------------------------------------------------------------------- /src/UI/DesktopClearArchitecture.Client/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  18 | 19 | 20 | 21 | 22 | 23 | 24 |