├── .gitignore ├── AspnetRunSpa.sln ├── LICENSE ├── README.md ├── src ├── AspnetRun.Application │ ├── AspnetRun.Application.csproj │ ├── Authorization │ │ ├── IAuthorizationService.cs │ │ └── NullAuthorizationService.cs │ ├── Dtos │ │ ├── BaseDto.cs │ │ ├── CategoryDto.cs │ │ ├── Issue │ │ │ ├── AddCommentInput.cs │ │ │ ├── AssignIssueToUserInput.cs │ │ │ ├── GetIssueInput.cs │ │ │ └── GetIssueOutput.cs │ │ ├── IssueCommentDto.cs │ │ ├── IssueDto.cs │ │ ├── ProductDto.cs │ │ └── UserDto.cs │ ├── Exceptions │ │ └── ApplicationException.cs │ ├── Interfaces │ │ ├── ICategoryAppService.cs │ │ ├── IIssueAppService.cs │ │ └── IProductAppService.cs │ ├── Mapper │ │ └── ObjectMapper.cs │ ├── Notification │ │ └── IUserEmailer.cs │ ├── Services │ │ ├── CategoryAppService.cs │ │ ├── IssueAppService.cs │ │ └── ProductAppService.cs │ ├── Session │ │ ├── ISessionService.cs │ │ └── NullSessionService.cs │ └── Validation │ │ └── IValidationService.cs ├── AspnetRun.Core │ ├── AspnetRun.Core.csproj │ ├── AspnetRunSettings.cs │ ├── Check.cs │ ├── Configuration │ │ └── IIssueAssignmentConfiguration.cs │ ├── Entities │ │ ├── BaseEntity.cs │ │ ├── Category.cs │ │ ├── Issue.cs │ │ ├── IssueCloseReason.cs │ │ ├── IssueComment.cs │ │ ├── Product.cs │ │ └── User.cs │ ├── Exceptions │ │ ├── CoreException.cs │ │ ├── IssueAssignmentException.cs │ │ └── IssueLockedException.cs │ ├── Interfaces │ │ ├── IAggregateRoot.cs │ │ ├── IAppLogger.cs │ │ ├── IAsyncRepository.cs │ │ ├── ICategoryRepository.cs │ │ ├── IIssueRepository.cs │ │ ├── IProductRepository.cs │ │ └── ISpecification.cs │ ├── Policy │ │ ├── IIssueAssignmentPolicy.cs │ │ └── IssueAssignmentPolicy.cs │ ├── Specifications │ │ ├── BaseSpecification.cs │ │ └── CategoryWithproductsSpecification.cs │ └── ValueObjects │ │ └── ValueObject.cs ├── AspnetRun.Infrastructure │ ├── AspnetRun.Infrastructure.csproj │ ├── Data │ │ ├── AspnetRunContext.cs │ │ └── AspnetRunContextSeed.cs │ ├── Exceptions │ │ └── InfrastructureException.cs │ ├── Logging │ │ └── LoggerAdapter.cs │ ├── Migrations │ │ ├── 20190321052921_Initial.Designer.cs │ │ ├── 20190321052921_Initial.cs │ │ └── AspnetRunContextModelSnapshot.cs │ ├── Repository │ │ ├── AspnetRunRepository.cs │ │ ├── CategoryRepository.cs │ │ ├── IssueRepository.cs │ │ ├── ProductRepository.cs │ │ └── SpecificationEvaluator.cs │ └── Services │ │ └── EmailSender.cs └── AspnetRun.Web │ ├── AspnetRun.Web.csproj │ ├── Components │ ├── App.razor │ ├── Pages │ │ ├── Counter.razor │ │ ├── FetchData.razor │ │ ├── Index.razor │ │ └── _ViewImports.cshtml │ ├── Shared │ │ ├── MainLayout.razor │ │ └── NavMenu.razor │ └── _ViewImports.cshtml │ ├── Pages │ ├── Index.cshtml │ └── _ViewImports.cshtml │ ├── Program.cs │ ├── Services │ ├── IssueComponentService.cs │ ├── WeatherForecast.cs │ └── WeatherForecastService.cs │ ├── Startup.cs │ ├── ViewModel │ └── IssueDetailViewModel.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ ├── css │ ├── bootstrap │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ ├── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ │ ├── css │ │ │ └── open-iconic-bootstrap.min.css │ │ │ └── fonts │ │ │ ├── open-iconic.eot │ │ │ ├── open-iconic.otf │ │ │ ├── open-iconic.svg │ │ │ ├── open-iconic.ttf │ │ │ └── open-iconic.woff │ └── site.css │ └── favicon.ico └── test ├── AspnetRun.Application.Tests ├── AspnetRun.Application.Tests.csproj └── Services │ └── DeleteProduct.cs ├── AspnetRun.Core.Tests ├── AspnetRun.Core.Tests.csproj ├── Entities │ ├── CategoryAddProduct.cs │ └── ProductCreate.cs └── ProductWithCategoryBuilder.cs └── AspnetRun.Infrastructure.Tests ├── AspnetRun.Infrastructure.Tests.csproj ├── Builders └── ProductBuilder.cs └── Repositories └── ProductGetById.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /AspnetRunSpa.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28729.10 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{01A3BD1A-5FB5-4DAC-8AE5-E04D052DAB13}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7CC4033C-35ED-4D93-B4CD-68FF0AF7CB91}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspnetRun.Core", "src\AspnetRun.Core\AspnetRun.Core.csproj", "{2CC7AC1C-DDA2-4BF5-8D46-E8881DAA811E}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspnetRun.Application", "src\AspnetRun.Application\AspnetRun.Application.csproj", "{13D34DC5-E8C8-4299-9FAE-619CCDD6ED0B}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspnetRun.Infrastructure", "src\AspnetRun.Infrastructure\AspnetRun.Infrastructure.csproj", "{7CB23810-1F4B-4940-891C-59707F2D1A72}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspnetRun.Core.Tests", "test\AspnetRun.Core.Tests\AspnetRun.Core.Tests.csproj", "{99500910-759E-4BB0-80CE-7A0253100ABC}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspnetRun.Infrastructure.Tests", "test\AspnetRun.Infrastructure.Tests\AspnetRun.Infrastructure.Tests.csproj", "{A45DAD46-157E-4B24-99B9-9A8334FA7945}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspnetRun.Application.Tests", "test\AspnetRun.Application.Tests\AspnetRun.Application.Tests.csproj", "{0889FF01-29BA-4387-A64C-2E60199C9544}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspnetRun.Web", "src\AspnetRun.Web\AspnetRun.Web.csproj", "{FFD0D787-7CC0-4CC1-A67B-BD1785E9DAAD}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {2CC7AC1C-DDA2-4BF5-8D46-E8881DAA811E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {2CC7AC1C-DDA2-4BF5-8D46-E8881DAA811E}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {2CC7AC1C-DDA2-4BF5-8D46-E8881DAA811E}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {2CC7AC1C-DDA2-4BF5-8D46-E8881DAA811E}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {13D34DC5-E8C8-4299-9FAE-619CCDD6ED0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {13D34DC5-E8C8-4299-9FAE-619CCDD6ED0B}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {13D34DC5-E8C8-4299-9FAE-619CCDD6ED0B}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {13D34DC5-E8C8-4299-9FAE-619CCDD6ED0B}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {7CB23810-1F4B-4940-891C-59707F2D1A72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {7CB23810-1F4B-4940-891C-59707F2D1A72}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {7CB23810-1F4B-4940-891C-59707F2D1A72}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {7CB23810-1F4B-4940-891C-59707F2D1A72}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {99500910-759E-4BB0-80CE-7A0253100ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {99500910-759E-4BB0-80CE-7A0253100ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {99500910-759E-4BB0-80CE-7A0253100ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {99500910-759E-4BB0-80CE-7A0253100ABC}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {A45DAD46-157E-4B24-99B9-9A8334FA7945}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {A45DAD46-157E-4B24-99B9-9A8334FA7945}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {A45DAD46-157E-4B24-99B9-9A8334FA7945}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {A45DAD46-157E-4B24-99B9-9A8334FA7945}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {0889FF01-29BA-4387-A64C-2E60199C9544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {0889FF01-29BA-4387-A64C-2E60199C9544}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {0889FF01-29BA-4387-A64C-2E60199C9544}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {0889FF01-29BA-4387-A64C-2E60199C9544}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {FFD0D787-7CC0-4CC1-A67B-BD1785E9DAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {FFD0D787-7CC0-4CC1-A67B-BD1785E9DAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {FFD0D787-7CC0-4CC1-A67B-BD1785E9DAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {FFD0D787-7CC0-4CC1-A67B-BD1785E9DAAD}.Release|Any CPU.Build.0 = Release|Any CPU 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(NestedProjects) = preSolution 63 | {2CC7AC1C-DDA2-4BF5-8D46-E8881DAA811E} = {01A3BD1A-5FB5-4DAC-8AE5-E04D052DAB13} 64 | {13D34DC5-E8C8-4299-9FAE-619CCDD6ED0B} = {01A3BD1A-5FB5-4DAC-8AE5-E04D052DAB13} 65 | {7CB23810-1F4B-4940-891C-59707F2D1A72} = {01A3BD1A-5FB5-4DAC-8AE5-E04D052DAB13} 66 | {99500910-759E-4BB0-80CE-7A0253100ABC} = {7CC4033C-35ED-4D93-B4CD-68FF0AF7CB91} 67 | {A45DAD46-157E-4B24-99B9-9A8334FA7945} = {7CC4033C-35ED-4D93-B4CD-68FF0AF7CB91} 68 | {0889FF01-29BA-4387-A64C-2E60199C9544} = {7CC4033C-35ED-4D93-B4CD-68FF0AF7CB91} 69 | {FFD0D787-7CC0-4CC1-A67B-BD1785E9DAAD} = {01A3BD1A-5FB5-4DAC-8AE5-E04D052DAB13} 70 | EndGlobalSection 71 | GlobalSection(ExtensibilityGlobals) = postSolution 72 | SolutionGuid = {B8BE9427-55CD-4306-809D-6512B49A7F30} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 aspnetrun 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 | This repository is ***under development***. It is **not finished yet**. 2 | 3 | # What is AspnetRun ? 4 | A **starter kit** for your next **ASP.NET Core** web application. Boilerplate for **ASP.NET Core reference application** with **Entity Framework Core**, demonstrating a layered application architecture with DDD best practices. Implements NLayer **Hexagonal architecture** (Core, Application, Infrastructure and Presentation Layers) and **Domain Driven Design** (Entities, Repositories, Domain/Application Services, DTO's...) 5 | and aimed to be a **Clean Architecture**, with applying **SOLID principles** in order to use for a project template. 6 | Also implements **best practices** like **loosely-coupled, dependency-inverted** architecture and using **design patterns** such as **Dependency Injection**, logging, validation, exception handling, localization and so on. 7 | 8 | You can check full repository documentations and step by step development of **[100+ page eBook PDF](http://www.aspnetrun.com/Book)** from here - **http://www.aspnetrun.com/Book**. Also detail introduction of book and project structure exists on **[50+ page of github wiki](https://github.com/aspnetrun/run-core/wiki)**. 9 | 10 | # AspnetRun run-aspnetcore-spa Repository 11 | New .Net Core 3.0 Asp.Net Razor Components SPA Web Application. 12 | 13 | * **[run-aspnetcore-spa](https://github.com/aspnetrun/run-aspnetcore-spa)** - intented to building Single-Page Web Applications(SPA) using ASP.NET Core & EF.Core and **Razor Components** templates with new aspnet core server-side rendering approach and build real-time communication with SignalR. **YOU ARE HERE.** 14 | 15 | This repository is ***under development***. It is **not finished yet**. 16 | It will become issue tracker system Real world example of run-core based application. 17 | 18 | * spa 19 | * razor components 20 | * identity 21 | * validation 22 | * exception management 23 | * issue tracker use cases like github 24 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/AspnetRun.Application.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Authorization/IAuthorizationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Authorization 6 | { 7 | public interface IAuthorizationService 8 | { 9 | void CheckPermission(string permissionName); 10 | void CheckPermission(string permissionName, int entityId); 11 | void CheckLogin(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Authorization/NullAuthorizationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Authorization 6 | { 7 | public class NullAuthorizationService : IAuthorizationService 8 | { 9 | public static NullAuthorizationService Instance { get; } = new NullAuthorizationService(); 10 | 11 | private NullAuthorizationService() 12 | { 13 | } 14 | 15 | public void CheckPermission(string permissionName) 16 | { 17 | 18 | } 19 | 20 | public void CheckPermission(string permissionName, int entityId) 21 | { 22 | 23 | } 24 | 25 | public void CheckLogin() 26 | { 27 | 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/BaseDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Dtos 6 | { 7 | public class BaseDto 8 | { 9 | public int Id { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/CategoryDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Dtos 6 | { 7 | public class CategoryDto : BaseDto 8 | { 9 | public string CategoryName { get; set; } 10 | public string Description { get; set; } 11 | public ICollection Products { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/Issue/AddCommentInput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Application.Dtos.Issue 7 | { 8 | public class AddCommentInput 9 | { 10 | [Required] 11 | [MaxLength(128)] 12 | public int IssueId { get; set; } 13 | 14 | [Required] 15 | [MaxLength(65536)] 16 | public string Message { get; protected set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/Issue/AssignIssueToUserInput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Application.Dtos.Issue 7 | { 8 | public class AssignIssueToUserInput 9 | { 10 | [Required] 11 | [MaxLength(128)] 12 | public int IssueId { get; set; } 13 | 14 | [Required] 15 | [MaxLength(128)] 16 | public int UserId { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/Issue/GetIssueInput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Dtos.Issue 6 | { 7 | public class GetIssueInput 8 | { 9 | public int Id { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/Issue/GetIssueOutput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Dtos.Issue 6 | { 7 | public class GetIssueOutput 8 | { 9 | public IssueDto Issue { get; set; } 10 | public List Comments { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/IssueCommentDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Dtos 6 | { 7 | public class IssueCommentDto : BaseDto 8 | { 9 | public string Message { get; set; } 10 | public UserDto CreatorUser { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/IssueDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Dtos 6 | { 7 | public class IssueDto : BaseDto 8 | { 9 | public string Title { get; set; } 10 | public string Body { get; set; } 11 | public bool IsClosed { get; set; } 12 | public bool IsLocked { get; set; } 13 | public string CloseReason { get; set; } 14 | public UserDto CreatorUser { get; set; } 15 | //public List Comments { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/ProductDto.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using AutoMapper; 3 | 4 | namespace AspnetRun.Application.Dtos 5 | { 6 | 7 | public class ProductDto : BaseDto 8 | { 9 | public string ProductName { get; set; } 10 | public string QuantityPerUnit { get; set; } 11 | public decimal? UnitPrice { get; set; } 12 | public short? UnitsInStock { get; set; } 13 | public short? UnitsOnOrder { get; set; } 14 | public short? ReorderLevel { get; set; } 15 | public bool Discontinued { get; set; } 16 | public int? CategoryId { get; set; } 17 | public CategoryDto Category { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Dtos/UserDto.cs: -------------------------------------------------------------------------------- 1 | namespace AspnetRun.Application.Dtos 2 | { 3 | public class UserDto : BaseDto 4 | { 5 | public string UserName { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/AspnetRun.Application/Exceptions/ApplicationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Exceptions 6 | { 7 | public class ApplicationException : Exception 8 | { 9 | internal ApplicationException(string businessMessage) 10 | : base(businessMessage) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Interfaces/ICategoryAppService.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Dtos; 2 | using AspnetRun.Core.Entities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace AspnetRun.Application.Interfaces 9 | { 10 | public interface ICategoryAppService 11 | { 12 | Task> GetCategoryList(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Interfaces/IIssueAppService.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Dtos.Issue; 2 | using System.Threading.Tasks; 3 | 4 | namespace AspnetRun.Application.Interfaces 5 | { 6 | public interface IIssueAppService 7 | { 8 | Task AssignIssueToUser(AssignIssueToUserInput input); 9 | Task AddComment(AddCommentInput input); 10 | Task GetIssue(GetIssueInput input); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Interfaces/IProductAppService.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Dtos; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace AspnetRun.Application.Interfaces 6 | { 7 | public interface IProductAppService 8 | { 9 | Task> GetProductList(); 10 | Task GetProductById(int productId); 11 | Task> GetProductByName(string productName); 12 | Task> GetProductByCategory(int categoryId); 13 | Task Create(ProductDto entityDto); 14 | Task Update(ProductDto entityDto); 15 | Task Delete(ProductDto entityDto); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Mapper/ObjectMapper.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Dtos; 2 | using AspnetRun.Core.Entities; 3 | using AutoMapper; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace AspnetRun.Application.Mapper 9 | { 10 | // The best implementation of AutoMapper for class libraries - https://stackoverflow.com/questions/26458731/how-to-configure-auto-mapper-in-class-library-project 11 | public class ObjectMapper 12 | { 13 | public static IMapper Mapper 14 | { 15 | get 16 | { 17 | return AutoMapper.Mapper.Instance; 18 | } 19 | } 20 | static ObjectMapper() 21 | { 22 | CreateMap(); 23 | } 24 | 25 | private static void CreateMap() 26 | { 27 | AutoMapper.Mapper.Initialize(cfg => 28 | { 29 | cfg.CreateMap() 30 | .ForMember(dest => dest.ProductName, opt => opt.MapFrom(src => src.ProductName)).ReverseMap(); 31 | cfg.CreateMap().ReverseMap(); 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Notification/IUserEmailer.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Application.Notification 7 | { 8 | public interface IUserEmailer 9 | { 10 | void IssueAssigned(User user, Issue issue); 11 | void AddedIssueComment(User user, Issue issue, IssueComment comment); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Services/CategoryAppService.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Dtos; 2 | using AspnetRun.Application.Mapper; 3 | using AspnetRun.Application.Interfaces; 4 | using AspnetRun.Core.Entities; 5 | using AspnetRun.Core.Interfaces; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace AspnetRun.Application.Services 12 | { 13 | public class CategoryAppService : ICategoryAppService 14 | { 15 | private readonly ICategoryRepository _categoryRepository; 16 | private readonly IAppLogger _logger; 17 | 18 | public CategoryAppService(ICategoryRepository categoryRepository, IAppLogger logger) 19 | { 20 | _categoryRepository = categoryRepository ?? throw new ArgumentNullException(nameof(categoryRepository)); 21 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 22 | } 23 | 24 | public async Task> GetCategoryList() 25 | { 26 | var category = await _categoryRepository.GetAllAsync(); 27 | var mapped = ObjectMapper.Mapper.Map>(category); 28 | return mapped; 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Services/IssueAppService.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Authorization; 2 | using AspnetRun.Application.Dtos; 3 | using AspnetRun.Application.Dtos.Issue; 4 | using AspnetRun.Application.Interfaces; 5 | using AspnetRun.Application.Mapper; 6 | using AspnetRun.Application.Notification; 7 | using AspnetRun.Application.Session; 8 | using AspnetRun.Application.Validation; 9 | using AspnetRun.Core.Entities; 10 | using AspnetRun.Core.Interfaces; 11 | using AspnetRun.Core.Policy; 12 | using System; 13 | using System.Threading.Tasks; 14 | 15 | namespace AspnetRun.Application.Services 16 | { 17 | public class IssueAppService : IIssueAppService 18 | { 19 | private readonly IIssueRepository _issueRepository; 20 | private readonly IAsyncRepository _userRepository; 21 | private readonly IIssueAssignmentPolicy _issueAssignmentPolicy; 22 | 23 | private readonly IValidationService _validationService; 24 | private readonly IAppLogger _logger; 25 | private readonly IUserEmailer _userEmailer; 26 | 27 | public IAuthorizationService AuthorizationService { get; set; } 28 | public ISessionService SessionService { get; set; } 29 | 30 | public IssueAppService(IIssueRepository issueRepository, IAsyncRepository userRepository, IIssueAssignmentPolicy issueAssignmentPolicy, IValidationService validationService, IAppLogger logger, IUserEmailer userEmailer) 31 | { 32 | _issueRepository = issueRepository ?? throw new ArgumentNullException(nameof(issueRepository)); 33 | _userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository)); 34 | _issueAssignmentPolicy = issueAssignmentPolicy ?? throw new ArgumentNullException(nameof(issueAssignmentPolicy)); 35 | _validationService = validationService ?? throw new ArgumentNullException(nameof(validationService)); 36 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 37 | _userEmailer = userEmailer ?? throw new ArgumentNullException(nameof(userEmailer)); 38 | 39 | AuthorizationService = NullAuthorizationService.Instance; 40 | SessionService = NullSessionService.Instance; 41 | } 42 | 43 | public async Task AssignIssueToUser(AssignIssueToUserInput input) 44 | { 45 | // authorization 46 | AuthorizationService.CheckPermission("TaskAssignmentPermission"); 47 | 48 | // validation 49 | _validationService.Validate(input); 50 | 51 | // domain 52 | var user = await _userRepository.GetByIdAsync(input.UserId); 53 | var issue = await _issueRepository.GetByIdAsync(input.IssueId); 54 | 55 | issue.AssignTo(user, _issueAssignmentPolicy); 56 | await _issueRepository.UpdateAsync(issue); 57 | 58 | 59 | // notification 60 | if (SessionService.UserId != user.Id) 61 | { 62 | _userEmailer.IssueAssigned(user, issue); 63 | } 64 | 65 | // logging 66 | _logger.LogInformation($"Assigned issue {issue} to user {user}"); 67 | } 68 | 69 | public async Task AddComment(AddCommentInput input) 70 | { 71 | AuthorizationService.CheckPermission("AddComment", input.IssueId); 72 | _validationService.Validate(input); 73 | 74 | var currentUser = await _userRepository.GetByIdAsync(SessionService.UserId); 75 | var issue = await _issueRepository.GetByIdAsync(input.IssueId); 76 | 77 | var comment = issue.AddComment(currentUser, input.Message); 78 | await _issueRepository.UpdateAsync(issue); 79 | 80 | _userEmailer.AddedIssueComment(currentUser, issue, comment); 81 | } 82 | 83 | public async Task GetIssue(GetIssueInput input) 84 | { 85 | AuthorizationService.CheckLogin(); 86 | _validationService.Validate(input); 87 | 88 | var issue = await _issueRepository.GetByIdAsync(input.Id); 89 | 90 | return new GetIssueOutput 91 | { 92 | Issue = ObjectMapper.Mapper.Map(issue), 93 | //Comments = GetIssueCommentDtos(issue.Id) 94 | }; 95 | } 96 | 97 | //private List GetIssueCommentDtos(string issueId) 98 | //{ 99 | // return _issueRepository 100 | // .GetCommentsWithCreatorUsers(issueId) 101 | // .Select(c => 102 | // { 103 | // var commentDto = _objectMapper.Map(c.Comment); 104 | // commentDto.CreatorUser = _objectMapper.Map(c.CreatorUser); 105 | // return commentDto; 106 | // }) 107 | // .ToList(); 108 | //} 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Services/ProductAppService.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Dtos; 2 | using AspnetRun.Application.Mapper; 3 | using AspnetRun.Application.Interfaces; 4 | using AspnetRun.Core.Entities; 5 | using AspnetRun.Core.Interfaces; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Threading.Tasks; 9 | 10 | namespace AspnetRun.Application.Services 11 | { 12 | // TODO : add validation , authorization, logging, exception handling etc. -- cross cutting activities in here. 13 | public class ProductAppService : IProductAppService 14 | { 15 | private readonly IProductRepository _productRepository; 16 | private readonly IAppLogger _logger; 17 | 18 | public ProductAppService(IProductRepository productRepository, IAppLogger logger) 19 | { 20 | _productRepository = productRepository ?? throw new ArgumentNullException(nameof(productRepository)); 21 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 22 | } 23 | 24 | public async Task> GetProductList() 25 | { 26 | var productList = await _productRepository.GetProductListAsync(); 27 | var mapped = ObjectMapper.Mapper.Map>(productList); 28 | return mapped; 29 | } 30 | 31 | public async Task GetProductById(int productId) 32 | { 33 | var product = await _productRepository.GetByIdAsync(productId); 34 | var mapped = ObjectMapper.Mapper.Map(product); 35 | return mapped; 36 | } 37 | 38 | public async Task> GetProductByName(string productName) 39 | { 40 | var productList = await _productRepository.GetProductByNameAsync(productName); 41 | var mapped = ObjectMapper.Mapper.Map>(productList); 42 | return mapped; 43 | } 44 | 45 | public async Task> GetProductByCategory(int categoryId) 46 | { 47 | var productList = await _productRepository.GetProductByCategoryAsync(categoryId); 48 | var mapped = ObjectMapper.Mapper.Map>(productList); 49 | return mapped; 50 | } 51 | 52 | public async Task Create(ProductDto entityDto) 53 | { 54 | await ValidateProductIfExist(entityDto); 55 | 56 | var mappedEntity = ObjectMapper.Mapper.Map(entityDto); 57 | if (mappedEntity == null) 58 | throw new ApplicationException($"Entity could not be mapped."); 59 | 60 | var newEntity = await _productRepository.AddAsync(mappedEntity); 61 | _logger.LogInformation($"Entity successfully added - AspnetRunAppService"); 62 | 63 | var newMappedEntity = ObjectMapper.Mapper.Map(newEntity); 64 | return newMappedEntity; 65 | } 66 | 67 | public async Task Update(ProductDto entityDto) 68 | { 69 | ValidateProductIfNotExist(entityDto); 70 | 71 | var mappedEntity = ObjectMapper.Mapper.Map(entityDto); 72 | if (mappedEntity == null) 73 | throw new ApplicationException($"Entity could not be mapped."); 74 | 75 | await _productRepository.UpdateAsync(mappedEntity); 76 | _logger.LogInformation($"Entity successfully updated - AspnetRunAppService"); 77 | } 78 | 79 | public async Task Delete(ProductDto entityDto) 80 | { 81 | ValidateProductIfNotExist(entityDto); 82 | 83 | var mappedEntity = ObjectMapper.Mapper.Map(entityDto); 84 | if (mappedEntity == null) 85 | throw new ApplicationException($"Entity could not be mapped."); 86 | 87 | await _productRepository.DeleteAsync(mappedEntity); 88 | _logger.LogInformation($"Entity successfully deleted - AspnetRunAppService"); 89 | } 90 | 91 | private async Task ValidateProductIfExist(ProductDto entityDto) 92 | { 93 | var existingEntity = await _productRepository.GetByIdAsync(entityDto.Id); 94 | if (existingEntity != null) 95 | throw new ApplicationException($"{entityDto.ToString()} with this id already exists"); 96 | } 97 | 98 | private void ValidateProductIfNotExist(ProductDto entityDto) 99 | { 100 | var existingEntity = _productRepository.GetByIdAsync(entityDto.Id); 101 | if (existingEntity == null) 102 | throw new ApplicationException($"{entityDto.ToString()} with this id is not exists"); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Session/ISessionService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Session 6 | { 7 | public interface ISessionService 8 | { 9 | int UserId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Session/NullSessionService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Session 6 | { 7 | public class NullSessionService : ISessionService 8 | { 9 | public static NullSessionService Instance { get; } = new NullSessionService(); 10 | public int UserId { get; set; } 11 | 12 | private NullSessionService() 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AspnetRun.Application/Validation/IValidationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Application.Validation 6 | { 7 | public interface IValidationService 8 | { 9 | void Validate(object obj); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/AspnetRun.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/AspnetRunSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Core 6 | { 7 | public class AspnetRunSettings 8 | { 9 | public string ProductBaseUrl { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Check.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Core 7 | { 8 | [DebuggerStepThrough] 9 | public static class Check 10 | { 11 | public static T NotNull(T value, string parameterName) 12 | { 13 | if (value == null) 14 | { 15 | throw new ArgumentNullException(parameterName); 16 | } 17 | 18 | return value; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Configuration/IIssueAssignmentConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Core.Policy 6 | { 7 | // TODO : check tennbookings - this will be loaded from aspnet core DI - from appsettings.json 8 | public interface IIssueAssignmentConfiguration 9 | { 10 | int MaxConcurrentOpenIssueCountForAUser { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Entities/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Interfaces; 2 | using System; 3 | 4 | namespace AspnetRun.Core.Entities 5 | { 6 | public abstract class BaseEntity : IAggregateRoot 7 | { 8 | public int Id { get; protected set; } 9 | 10 | public override bool Equals(object obj) 11 | { 12 | var compareTo = obj as BaseEntity; 13 | 14 | if (ReferenceEquals(this, compareTo)) return true; 15 | if (ReferenceEquals(null, compareTo)) return false; 16 | 17 | return Id.Equals(compareTo.Id); 18 | } 19 | 20 | public static bool operator ==(BaseEntity a, BaseEntity b) 21 | { 22 | if (ReferenceEquals(a, null) && ReferenceEquals(b, null)) 23 | return true; 24 | 25 | if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) 26 | return false; 27 | 28 | return a.Equals(b); 29 | } 30 | 31 | public static bool operator !=(BaseEntity a, BaseEntity b) 32 | { 33 | return !(a == b); 34 | } 35 | 36 | public override int GetHashCode() 37 | { 38 | return (GetType().GetHashCode() * 907) + Id.GetHashCode(); 39 | } 40 | 41 | public override string ToString() 42 | { 43 | return GetType().Name + " [Id=" + Id + "]"; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Entities/Category.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Core.Entities 7 | { 8 | public class Category : BaseEntity 9 | { 10 | public Category() 11 | { 12 | Products = new HashSet(); 13 | } 14 | 15 | public string CategoryName { get; set; } 16 | public string Description { get; set; } 17 | public ICollection Products { get; private set; } 18 | 19 | public static Category Create(int categoryId, string name, string description = null) 20 | { 21 | var category = new Category 22 | { 23 | Id = categoryId, 24 | CategoryName = name, 25 | Description = description 26 | }; 27 | return category; 28 | } 29 | 30 | public void AddProduct(int productId, string productName) 31 | { 32 | if (!Products.Any(p => p.Id == productId)) 33 | { 34 | Products.Add(Product.Create(productId, this.Id, productName)); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Entities/Issue.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Exceptions; 2 | using AspnetRun.Core.Policy; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.Immutable; 6 | using System.Collections.ObjectModel; 7 | using System.ComponentModel.DataAnnotations; 8 | using System.Text; 9 | 10 | namespace AspnetRun.Core.Entities 11 | { 12 | public class Issue : BaseEntity 13 | { 14 | [Required] 15 | public string Title { get; protected set; } 16 | public string Body { get; protected set; } 17 | public bool IsClosed { get; protected set; } 18 | public bool IsLocked { get; protected set; } 19 | public IssueCloseReason? CloseReason { get; protected set; } 20 | [Required] 21 | public int CreatorUserId { get; protected set; } 22 | [Required] 23 | public int AssignedUserId { get; protected set; } 24 | public IReadOnlyList Comments => _comments.ToImmutableList(); 25 | protected virtual ICollection _comments { get; set; } 26 | 27 | protected Issue() 28 | { 29 | } 30 | 31 | public Issue([Required] int creatorUserId, [Required] string title, string body = null) 32 | { 33 | Check.NotNull(creatorUserId, nameof(creatorUserId)); 34 | Check.NotNull(title, nameof(title)); 35 | 36 | // Id = Guid.NewGuid().ToString("D"); 37 | 38 | CreatorUserId = creatorUserId; 39 | Title = title; 40 | Body = body; 41 | 42 | _comments = new Collection(); 43 | } 44 | 45 | public void AssignTo([Required] User user, [Required] IIssueAssignmentPolicy policy) 46 | { 47 | Check.NotNull(user, nameof(user)); 48 | Check.NotNull(policy, nameof(policy)); 49 | 50 | policy.CheckAssignment(this, user); 51 | 52 | AssignedUserId = user.Id; 53 | } 54 | 55 | public void ClearAssignment() 56 | { 57 | AssignedUserId = 0; 58 | } 59 | 60 | public IssueComment AddComment([Required] User creatorUser, [Required] string message) 61 | { 62 | Check.NotNull(creatorUser, nameof(creatorUser)); 63 | 64 | if (IsLocked) 65 | { 66 | throw new IssueLockedException(Id); 67 | } 68 | 69 | var comment = new IssueComment(creatorUser.Id, message); 70 | _comments.Add(comment); 71 | return comment; 72 | } 73 | 74 | public void Close(IssueCloseReason reason) 75 | { 76 | CloseReason = reason; 77 | IsClosed = true; 78 | } 79 | 80 | public void ReOpen() 81 | { 82 | IsClosed = false; 83 | CloseReason = null; 84 | } 85 | 86 | public void Lock() 87 | { 88 | if (!IsClosed) 89 | { 90 | throw new InvalidOperationException("An open issue can not be locked. Should be closed first!"); 91 | } 92 | 93 | IsLocked = true; 94 | } 95 | 96 | public void Unlock() 97 | { 98 | IsLocked = false; 99 | } 100 | 101 | public override string ToString() 102 | { 103 | return $"[Issue {Id}] {Title}"; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Entities/IssueCloseReason.cs: -------------------------------------------------------------------------------- 1 | namespace AspnetRun.Core.Entities 2 | { 3 | public enum IssueCloseReason 4 | { 5 | Fixed, 6 | WontFix, 7 | Invalid 8 | } 9 | } -------------------------------------------------------------------------------- /src/AspnetRun.Core/Entities/IssueComment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Core.Entities 7 | { 8 | public class IssueComment : BaseEntity 9 | { 10 | [Required] 11 | public string Message { get; protected set; } 12 | [Required] 13 | public int CreatorUserId { get; protected set; } 14 | public IssueComment([Required] int creatorUserId, [Required] string message) 15 | { 16 | Check.NotNull(creatorUserId, nameof(creatorUserId)); 17 | Check.NotNull(message, nameof(message)); 18 | 19 | Message = message; 20 | CreatorUserId = creatorUserId; 21 | } 22 | 23 | public override string ToString() 24 | { 25 | return $"[IssueComment {Id}] {Message}"; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Entities/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Core.Entities 6 | { 7 | public class Product : BaseEntity 8 | { 9 | public string ProductName { get; set; } 10 | public string QuantityPerUnit { get; set; } 11 | public decimal? UnitPrice { get; set; } 12 | public short? UnitsInStock { get; set; } 13 | public short? UnitsOnOrder { get; set; } 14 | public short? ReorderLevel { get; set; } 15 | public bool Discontinued { get; set; } 16 | public int CategoryId { get; set; } 17 | public string ProductReason { get; set; } 18 | public Category Category { get; set; } 19 | 20 | public static Product Create(int productId, int categoryId, string name, decimal? unitPrice = null, short? unitsInStock = null, short? unitsOnOrder = null, short? reorderLevel = null, bool discontinued = false) 21 | { 22 | var product = new Product 23 | { 24 | Id = productId, 25 | CategoryId = categoryId, 26 | ProductName = name, 27 | UnitPrice = unitPrice, 28 | UnitsInStock = unitsInStock, 29 | UnitsOnOrder = unitsOnOrder, 30 | ReorderLevel = reorderLevel, 31 | Discontinued = discontinued 32 | }; 33 | return product; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Entities/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Core.Entities 7 | { 8 | public class User : BaseEntity 9 | { 10 | [Required] 11 | public string UserName { get; protected set; } 12 | 13 | public User([Required] string userName) 14 | { 15 | UserName = userName ?? throw new ArgumentNullException(nameof(userName)); 16 | } 17 | 18 | public override string ToString() 19 | { 20 | return $"[User {Id}] {UserName}"; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Exceptions/CoreException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Core.Exceptions 6 | { 7 | public class CoreException : Exception 8 | { 9 | internal CoreException(string businessMessage) 10 | : base(businessMessage) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Exceptions/IssueAssignmentException.cs: -------------------------------------------------------------------------------- 1 | namespace AspnetRun.Core.Exceptions 2 | { 3 | public class IssueAssignmentException : CoreException 4 | { 5 | public IssueAssignmentException(string message) 6 | : base(message) 7 | { 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Exceptions/IssueLockedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Core.Exceptions 6 | { 7 | public class IssueLockedException : CoreException 8 | { 9 | public int IssueId { get; } 10 | 11 | public IssueLockedException(int issueId) 12 | : base(issueId.ToString()) 13 | { 14 | IssueId = issueId; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Interfaces/IAggregateRoot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Core.Interfaces 6 | { 7 | public interface IAggregateRoot 8 | { 9 | int Id { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Interfaces/IAppLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Core.Interfaces 6 | { 7 | public interface IAppLogger 8 | { 9 | void LogInformation(string message, params object[] args); 10 | void LogWarning(string message, params object[] args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Interfaces/IAsyncRepository.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace AspnetRun.Core.Interfaces 10 | { 11 | public interface IAsyncRepository where T : BaseEntity 12 | { 13 | Task> GetAllAsync(); 14 | Task> GetAsync(Expression> predicate); 15 | Task> GetAsync(Expression> predicate = null, 16 | Func, IOrderedQueryable> orderBy = null, 17 | string includeString = null, 18 | bool disableTracking = true); 19 | Task> GetAsync(Expression> predicate = null, 20 | Func, IOrderedQueryable> orderBy = null, 21 | List>> includes = null, 22 | bool disableTracking = true); 23 | Task> GetAsync(ISpecification spec); 24 | Task GetByIdAsync(int id); 25 | Task AddAsync(T entity); 26 | Task UpdateAsync(T entity); 27 | Task DeleteAsync(T entity); 28 | Task CountAsync(ISpecification spec); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Interfaces/ICategoryRepository.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspnetRun.Core.Interfaces 8 | { 9 | public interface ICategoryRepository : IAsyncRepository 10 | { 11 | Task GetCategoryWithProductsAsync(int categoryId); 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Interfaces/IIssueRepository.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Core.Interfaces 7 | { 8 | public interface IIssueRepository : IAsyncRepository 9 | { 10 | int GetOpenIssueCountOfUser(int userId); 11 | List GetCommentsWithCreatorUsers(int issueId); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Interfaces/IProductRepository.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspnetRun.Core.Interfaces 8 | { 9 | public interface IProductRepository : IAsyncRepository 10 | { 11 | Task> GetProductListAsync(); 12 | Task> GetProductByNameAsync(string productName); 13 | Task> GetProductByCategoryAsync(int categoryId); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Interfaces/ISpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace AspnetRun.Core.Interfaces 6 | { 7 | public interface ISpecification 8 | { 9 | Expression> Criteria { get; } 10 | List>> Includes { get; } 11 | List IncludeStrings { get; } 12 | Expression> OrderBy { get; } 13 | Expression> OrderByDescending { get; } 14 | 15 | int Take { get; } 16 | int Skip { get; } 17 | bool isPagingEnabled { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Policy/IIssueAssignmentPolicy.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Core.Policy 7 | { 8 | public interface IIssueAssignmentPolicy 9 | { 10 | void CheckAssignment(Issue issue, User user); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Policy/IssueAssignmentPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using AspnetRun.Core.Entities; 5 | using AspnetRun.Core.Exceptions; 6 | using AspnetRun.Core.Interfaces; 7 | 8 | namespace AspnetRun.Core.Policy 9 | { 10 | public class IssueAssignmentPolicy : IIssueAssignmentPolicy 11 | { 12 | private readonly IIssueRepository _issueRepository; 13 | private readonly IIssueAssignmentConfiguration _configuration; 14 | 15 | public IssueAssignmentPolicy(IIssueRepository issueRepository, IIssueAssignmentConfiguration configuration) 16 | { 17 | _issueRepository = issueRepository ?? throw new ArgumentNullException(nameof(issueRepository)); 18 | _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 19 | } 20 | 21 | public void CheckAssignment(Issue issue, User user) 22 | { 23 | if(_issueRepository.GetOpenIssueCountOfUser(user.Id) >= _configuration.MaxConcurrentOpenIssueCountForAUser) 24 | { 25 | throw new IssueAssignmentException($"Can not assign more than {_configuration.MaxConcurrentOpenIssueCountForAUser} open issues to a user!"); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Specifications/BaseSpecification.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Interfaces; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | 6 | namespace AspnetRun.Core.Specifications 7 | { 8 | // Specification Pattern from : https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-implemenation-entity-framework-core 9 | public abstract class BaseSpecification : ISpecification 10 | { 11 | protected BaseSpecification(Expression> criteria) 12 | { 13 | Criteria = criteria; 14 | } 15 | public Expression> Criteria { get; } 16 | public List>> Includes { get; } = new List>>(); 17 | public List IncludeStrings { get; } = new List(); 18 | public Expression> OrderBy { get; private set; } 19 | public Expression> OrderByDescending { get; private set; } 20 | 21 | public int Take { get; private set; } 22 | public int Skip { get; private set; } 23 | public bool isPagingEnabled { get; private set; } = false; 24 | 25 | protected virtual void AddInclude(Expression> includeExpression) 26 | { 27 | Includes.Add(includeExpression); 28 | } 29 | protected virtual void AddInclude(string includeString) 30 | { 31 | IncludeStrings.Add(includeString); 32 | } 33 | protected virtual void ApplyPaging(int skip, int take) 34 | { 35 | Skip = skip; 36 | Take = take; 37 | isPagingEnabled = true; 38 | } 39 | protected virtual void ApplyOrderBy(Expression> orderByExpression) 40 | { 41 | OrderBy = orderByExpression; 42 | } 43 | protected virtual void ApplyOrderByDescending(Expression> orderByDescendingExpression) 44 | { 45 | OrderByDescending = orderByDescendingExpression; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/Specifications/CategoryWithproductsSpecification.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Core.Specifications 7 | { 8 | public sealed class CategoryWithProductsSpecification : BaseSpecification 9 | { 10 | public CategoryWithProductsSpecification(int categoryId) 11 | : base(b => b.Id == categoryId) 12 | { 13 | AddInclude(b => b.Products); 14 | } 15 | } 16 | 17 | public class ProductWithCategorySpecification : BaseSpecification 18 | { 19 | public ProductWithCategorySpecification(string productName) 20 | : base(p => p.ProductName.ToLower().Contains(productName.ToLower())) 21 | { 22 | AddInclude(p => p.Category); 23 | } 24 | 25 | public ProductWithCategorySpecification() : base(null) 26 | { 27 | AddInclude(p => p.Category); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AspnetRun.Core/ValueObjects/ValueObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Core.ValueObjects 7 | { 8 | // Source: https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/implement-value-objects 9 | public abstract class ValueObject 10 | { 11 | protected static bool EqualOperator(ValueObject left, ValueObject right) 12 | { 13 | if (left is null ^ right is null) 14 | { 15 | return false; 16 | } 17 | 18 | return left?.Equals(right) != false; 19 | } 20 | 21 | protected static bool NotEqualOperator(ValueObject left, ValueObject right) 22 | { 23 | return !(EqualOperator(left, right)); 24 | } 25 | 26 | protected abstract IEnumerable GetAtomicValues(); 27 | 28 | public override bool Equals(object obj) 29 | { 30 | if (obj == null || obj.GetType() != GetType()) 31 | { 32 | return false; 33 | } 34 | 35 | var other = (ValueObject)obj; 36 | var thisValues = GetAtomicValues().GetEnumerator(); 37 | var otherValues = other.GetAtomicValues().GetEnumerator(); 38 | 39 | while (thisValues.MoveNext() && otherValues.MoveNext()) 40 | { 41 | if (thisValues.Current is null ^ otherValues.Current is null) 42 | { 43 | return false; 44 | } 45 | 46 | if (thisValues.Current != null && 47 | !thisValues.Current.Equals(otherValues.Current)) 48 | { 49 | return false; 50 | } 51 | } 52 | 53 | return !thisValues.MoveNext() && !otherValues.MoveNext(); 54 | } 55 | 56 | public override int GetHashCode() 57 | { 58 | return GetAtomicValues() 59 | .Select(x => x != null ? x.GetHashCode() : 0) 60 | .Aggregate((x, y) => x ^ y); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/AspnetRun.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Data/AspnetRunContext.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace AspnetRun.Infrastructure.Data 6 | { 7 | public class AspnetRunContext : DbContext 8 | { 9 | public AspnetRunContext(DbContextOptions options) : base(options) 10 | { 11 | } 12 | 13 | public DbSet Products { get; set; } 14 | public DbSet Categories { get; set; } 15 | 16 | protected override void OnModelCreating(ModelBuilder builder) 17 | { 18 | builder.Entity(ConfigureProduct); 19 | builder.Entity(ConfigureCategory); 20 | } 21 | 22 | private void ConfigureProduct(EntityTypeBuilder builder) 23 | { 24 | builder.ToTable("Product"); 25 | 26 | builder.HasKey(ci => ci.Id); 27 | 28 | builder.Property(ci => ci.Id) 29 | .ForSqlServerUseSequenceHiLo("aspnetrun_type_hilo") 30 | .IsRequired(); 31 | 32 | builder.Property(cb => cb.ProductName) 33 | .IsRequired() 34 | .HasMaxLength(100); 35 | } 36 | 37 | private void ConfigureCategory(EntityTypeBuilder builder) 38 | { 39 | builder.ToTable("Category"); 40 | 41 | builder.HasKey(ci => ci.Id); 42 | 43 | builder.Property(ci => ci.Id) 44 | .ForSqlServerUseSequenceHiLo("aspnetrun_type_hilo") 45 | .IsRequired(); 46 | 47 | builder.Property(cb => cb.CategoryName) 48 | .IsRequired() 49 | .HasMaxLength(100); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Data/AspnetRunContextSeed.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace AspnetRun.Infrastructure.Data 10 | { 11 | public class AspnetRunContextSeed 12 | { 13 | public static async Task SeedAsync(AspnetRunContext aspnetrunContext, ILoggerFactory loggerFactory, int? retry = 0) 14 | { 15 | int retryForAvailability = retry.Value; 16 | 17 | try 18 | { 19 | // TODO: Only run this if using a real database 20 | // aspnetrunContext.Database.Migrate(); 21 | // aspnetrunContext.Database.EnsureCreated(); 22 | 23 | if (!aspnetrunContext.Categories.Any()) 24 | { 25 | aspnetrunContext.Categories.AddRange(GetPreconfiguredCategories()); 26 | await aspnetrunContext.SaveChangesAsync(); 27 | } 28 | 29 | if (!aspnetrunContext.Products.Any()) 30 | { 31 | aspnetrunContext.Products.AddRange(GetPreconfiguredProducts()); 32 | await aspnetrunContext.SaveChangesAsync(); 33 | } 34 | } 35 | catch (Exception exception) 36 | { 37 | if (retryForAvailability < 10) 38 | { 39 | retryForAvailability++; 40 | var log = loggerFactory.CreateLogger(); 41 | log.LogError(exception.Message); 42 | await SeedAsync(aspnetrunContext, loggerFactory, retryForAvailability); 43 | } 44 | throw; 45 | } 46 | } 47 | 48 | private static IEnumerable GetPreconfiguredCategories() 49 | { 50 | return new List() 51 | { 52 | new Category() { CategoryName = "Phone"}, 53 | new Category() { CategoryName = "TV"} 54 | }; 55 | } 56 | 57 | private static IEnumerable GetPreconfiguredProducts() 58 | { 59 | return new List() 60 | { 61 | new Product() { ProductName = "IPhone", CategoryId = 1 , UnitPrice = 19.5M , UnitsInStock = 10, QuantityPerUnit = "2", UnitsOnOrder = 1, ReorderLevel = 1, Discontinued = false }, 62 | new Product() { ProductName = "Samsung", CategoryId = 1 , UnitPrice = 33.5M , UnitsInStock = 10, QuantityPerUnit = "2", UnitsOnOrder = 1, ReorderLevel = 1, Discontinued = false }, 63 | new Product() { ProductName = "LG TV", CategoryId = 2 , UnitPrice = 33.5M , UnitsInStock = 10, QuantityPerUnit = "2", UnitsOnOrder = 1, ReorderLevel = 1, Discontinued = false } 64 | }; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Exceptions/InfrastructureException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AspnetRun.Infrastructure.Exceptions 6 | { 7 | public class InfrastructureException : Exception 8 | { 9 | internal InfrastructureException(string businessMessage) 10 | : base(businessMessage) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Logging/LoggerAdapter.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Interfaces; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace AspnetRun.Infrastructure.Logging 5 | { 6 | public class LoggerAdapter : IAppLogger 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public LoggerAdapter(ILoggerFactory loggerFactory) 11 | { 12 | _logger = loggerFactory.CreateLogger(); 13 | } 14 | 15 | public void LogWarning(string message, params object[] args) 16 | { 17 | _logger.LogWarning(message, args); 18 | } 19 | 20 | public void LogInformation(string message, params object[] args) 21 | { 22 | _logger.LogInformation(message, args); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Migrations/20190321052921_Initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using AspnetRun.Infrastructure.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | namespace AspnetRun.Infrastructure.Migrations 11 | { 12 | [DbContext(typeof(AspnetRunContext))] 13 | [Migration("20190321052921_Initial")] 14 | partial class Initial 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.2.0-rtm-35687") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("Relational:Sequence:.aspnetrun_type_hilo", "'aspnetrun_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") 23 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 24 | 25 | modelBuilder.Entity("AspnetRun.Core.Entities.Category", b => 26 | { 27 | b.Property("Id") 28 | .ValueGeneratedOnAdd() 29 | .HasAnnotation("SqlServer:HiLoSequenceName", "aspnetrun_type_hilo") 30 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); 31 | 32 | b.Property("CategoryName") 33 | .IsRequired() 34 | .HasMaxLength(100); 35 | 36 | b.Property("Description"); 37 | 38 | b.HasKey("Id"); 39 | 40 | b.ToTable("Category"); 41 | }); 42 | 43 | modelBuilder.Entity("AspnetRun.Core.Entities.Product", b => 44 | { 45 | b.Property("Id") 46 | .ValueGeneratedOnAdd() 47 | .HasAnnotation("SqlServer:HiLoSequenceName", "aspnetrun_type_hilo") 48 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); 49 | 50 | b.Property("CategoryId"); 51 | 52 | b.Property("Discontinued"); 53 | 54 | b.Property("ProductName") 55 | .IsRequired() 56 | .HasMaxLength(100); 57 | 58 | b.Property("QuantityPerUnit"); 59 | 60 | b.Property("ReorderLevel"); 61 | 62 | b.Property("UnitPrice"); 63 | 64 | b.Property("UnitsInStock"); 65 | 66 | b.Property("UnitsOnOrder"); 67 | 68 | b.HasKey("Id"); 69 | 70 | b.HasIndex("CategoryId"); 71 | 72 | b.ToTable("Product"); 73 | }); 74 | 75 | modelBuilder.Entity("AspnetRun.Core.Entities.Product", b => 76 | { 77 | b.HasOne("AspnetRun.Core.Entities.Category", "Category") 78 | .WithMany("Products") 79 | .HasForeignKey("CategoryId") 80 | .OnDelete(DeleteBehavior.Cascade); 81 | }); 82 | #pragma warning restore 612, 618 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Migrations/20190321052921_Initial.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace AspnetRun.Infrastructure.Migrations 4 | { 5 | public partial class Initial : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | migrationBuilder.CreateSequence( 10 | name: "aspnetrun_type_hilo", 11 | incrementBy: 10); 12 | 13 | migrationBuilder.CreateTable( 14 | name: "Category", 15 | columns: table => new 16 | { 17 | Id = table.Column(nullable: false), 18 | CategoryName = table.Column(maxLength: 100, nullable: false), 19 | Description = table.Column(nullable: true) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_Category", x => x.Id); 24 | }); 25 | 26 | migrationBuilder.CreateTable( 27 | name: "Product", 28 | columns: table => new 29 | { 30 | Id = table.Column(nullable: false), 31 | ProductName = table.Column(maxLength: 100, nullable: false), 32 | QuantityPerUnit = table.Column(nullable: true), 33 | UnitPrice = table.Column(nullable: true), 34 | UnitsInStock = table.Column(nullable: true), 35 | UnitsOnOrder = table.Column(nullable: true), 36 | ReorderLevel = table.Column(nullable: true), 37 | Discontinued = table.Column(nullable: false), 38 | CategoryId = table.Column(nullable: false) 39 | }, 40 | constraints: table => 41 | { 42 | table.PrimaryKey("PK_Product", x => x.Id); 43 | table.ForeignKey( 44 | name: "FK_Product_Category_CategoryId", 45 | column: x => x.CategoryId, 46 | principalTable: "Category", 47 | principalColumn: "Id", 48 | onDelete: ReferentialAction.Cascade); 49 | }); 50 | 51 | migrationBuilder.CreateIndex( 52 | name: "IX_Product_CategoryId", 53 | table: "Product", 54 | column: "CategoryId"); 55 | } 56 | 57 | protected override void Down(MigrationBuilder migrationBuilder) 58 | { 59 | migrationBuilder.DropTable( 60 | name: "Product"); 61 | 62 | migrationBuilder.DropTable( 63 | name: "Category"); 64 | 65 | migrationBuilder.DropSequence( 66 | name: "aspnetrun_type_hilo"); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Migrations/AspnetRunContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using AspnetRun.Infrastructure.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | namespace AspnetRun.Infrastructure.Migrations 10 | { 11 | [DbContext(typeof(AspnetRunContext))] 12 | partial class AspnetRunContextModelSnapshot : ModelSnapshot 13 | { 14 | protected override void BuildModel(ModelBuilder modelBuilder) 15 | { 16 | #pragma warning disable 612, 618 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "2.2.0-rtm-35687") 19 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 20 | .HasAnnotation("Relational:Sequence:.aspnetrun_type_hilo", "'aspnetrun_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") 21 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 22 | 23 | modelBuilder.Entity("AspnetRun.Core.Entities.Category", b => 24 | { 25 | b.Property("Id") 26 | .ValueGeneratedOnAdd() 27 | .HasAnnotation("SqlServer:HiLoSequenceName", "aspnetrun_type_hilo") 28 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); 29 | 30 | b.Property("CategoryName") 31 | .IsRequired() 32 | .HasMaxLength(100); 33 | 34 | b.Property("Description"); 35 | 36 | b.HasKey("Id"); 37 | 38 | b.ToTable("Category"); 39 | }); 40 | 41 | modelBuilder.Entity("AspnetRun.Core.Entities.Product", b => 42 | { 43 | b.Property("Id") 44 | .ValueGeneratedOnAdd() 45 | .HasAnnotation("SqlServer:HiLoSequenceName", "aspnetrun_type_hilo") 46 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); 47 | 48 | b.Property("CategoryId"); 49 | 50 | b.Property("Discontinued"); 51 | 52 | b.Property("ProductName") 53 | .IsRequired() 54 | .HasMaxLength(100); 55 | 56 | b.Property("QuantityPerUnit"); 57 | 58 | b.Property("ReorderLevel"); 59 | 60 | b.Property("UnitPrice"); 61 | 62 | b.Property("UnitsInStock"); 63 | 64 | b.Property("UnitsOnOrder"); 65 | 66 | b.HasKey("Id"); 67 | 68 | b.HasIndex("CategoryId"); 69 | 70 | b.ToTable("Product"); 71 | }); 72 | 73 | modelBuilder.Entity("AspnetRun.Core.Entities.Product", b => 74 | { 75 | b.HasOne("AspnetRun.Core.Entities.Category", "Category") 76 | .WithMany("Products") 77 | .HasForeignKey("CategoryId") 78 | .OnDelete(DeleteBehavior.Cascade); 79 | }); 80 | #pragma warning restore 612, 618 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Repository/AspnetRunRepository.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using AspnetRun.Core.Interfaces; 3 | using AspnetRun.Infrastructure.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace AspnetRun.Infrastructure.Repository 13 | { 14 | public class AspnetRunRepository : IAsyncRepository where T : BaseEntity 15 | { 16 | protected readonly AspnetRunContext _dbContext; 17 | 18 | public AspnetRunRepository(AspnetRunContext dbContext) 19 | { 20 | _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); 21 | } 22 | 23 | public async Task> GetAllAsync() 24 | { 25 | return await _dbContext.Set().ToListAsync(); 26 | } 27 | 28 | public async Task> GetAsync(ISpecification spec) 29 | { 30 | return await ApplySpecification(spec).ToListAsync(); 31 | } 32 | 33 | public async Task CountAsync(ISpecification spec) 34 | { 35 | return await ApplySpecification(spec).CountAsync(); 36 | } 37 | 38 | private IQueryable ApplySpecification(ISpecification spec) 39 | { 40 | return SpecificationEvaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); 41 | } 42 | 43 | public async Task> GetAsync(Expression> predicate) 44 | { 45 | return await _dbContext.Set().Where(predicate).ToListAsync(); 46 | } 47 | 48 | public async Task> GetAsync(Expression> predicate = null, Func, IOrderedQueryable> orderBy = null, string includeString = null, bool disableTracking = true) 49 | { 50 | IQueryable query = _dbContext.Set(); 51 | if (disableTracking) query = query.AsNoTracking(); 52 | 53 | if (!string.IsNullOrWhiteSpace(includeString)) query = query.Include(includeString); 54 | 55 | if (predicate != null) query = query.Where(predicate); 56 | 57 | if (orderBy != null) 58 | return await orderBy(query).ToListAsync(); 59 | return await query.ToListAsync(); 60 | } 61 | 62 | public async Task> GetAsync(Expression> predicate = null, Func, IOrderedQueryable> orderBy = null, List>> includes = null, bool disableTracking = true) 63 | { 64 | IQueryable query = _dbContext.Set(); 65 | if (disableTracking) query = query.AsNoTracking(); 66 | 67 | if (includes != null) query = includes.Aggregate(query, (current, include) => current.Include(include)); 68 | 69 | if (predicate != null) query = query.Where(predicate); 70 | 71 | if (orderBy != null) 72 | return await orderBy(query).ToListAsync(); 73 | return await query.ToListAsync(); 74 | } 75 | 76 | public virtual async Task GetByIdAsync(int id) 77 | { 78 | return await _dbContext.Set().FindAsync(id); 79 | } 80 | 81 | public async Task AddAsync(T entity) 82 | { 83 | _dbContext.Set().Add(entity); 84 | await _dbContext.SaveChangesAsync(); 85 | return entity; 86 | } 87 | 88 | public async Task UpdateAsync(T entity) 89 | { 90 | _dbContext.Entry(entity).State = EntityState.Modified; 91 | await _dbContext.SaveChangesAsync(); 92 | } 93 | 94 | public async Task DeleteAsync(T entity) 95 | { 96 | _dbContext.Set().Remove(entity); 97 | await _dbContext.SaveChangesAsync(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Repository/CategoryRepository.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using AspnetRun.Core.Interfaces; 3 | using AspnetRun.Core.Specifications; 4 | using AspnetRun.Infrastructure.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace AspnetRun.Infrastructure.Repository 9 | { 10 | public class CategoryRepository : AspnetRunRepository, ICategoryRepository 11 | { 12 | public CategoryRepository(AspnetRunContext dbContext) : base(dbContext) 13 | { 14 | } 15 | 16 | public async Task GetCategoryWithProductsAsync(int categoryId) 17 | { 18 | var spec = new CategoryWithProductsSpecification(categoryId); 19 | var category = (await GetAsync(spec)).FirstOrDefault(); 20 | return category; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Repository/IssueRepository.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using AspnetRun.Core.Interfaces; 3 | using AspnetRun.Infrastructure.Data; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace AspnetRun.Infrastructure.Repository 9 | { 10 | public class IssueRepository : AspnetRunRepository, IIssueRepository 11 | { 12 | public IssueRepository(AspnetRunContext dbContext) : base(dbContext) 13 | { 14 | } 15 | 16 | public List GetCommentsWithCreatorUsers(int issueId) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | 21 | public int GetOpenIssueCountOfUser(int userId) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Repository/ProductRepository.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using AspnetRun.Core.Interfaces; 3 | using AspnetRun.Core.Specifications; 4 | using AspnetRun.Infrastructure.Data; 5 | using Microsoft.EntityFrameworkCore; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace AspnetRun.Infrastructure.Repository 13 | { 14 | public class ProductRepository : AspnetRunRepository, IProductRepository 15 | { 16 | public ProductRepository(AspnetRunContext dbContext) : base(dbContext) 17 | { 18 | } 19 | 20 | public async Task> GetProductListAsync() 21 | { 22 | // return await GetAllAsync(); 23 | 24 | var spec = new ProductWithCategorySpecification(); 25 | return await GetAsync(spec); 26 | } 27 | 28 | public async Task> GetProductByNameAsync(string productName) 29 | { 30 | var spec = new ProductWithCategorySpecification(productName); 31 | return await GetAsync(spec); 32 | 33 | // return await GetAsync(x => x.ProductName.ToLower().Contains(productName.ToLower())); 34 | 35 | //return await _dbContext.Products 36 | // .Where(x => x.ProductName.Contains(productName)) 37 | // .ToListAsync(); 38 | } 39 | 40 | public async Task> GetProductByCategoryAsync(int categoryId) 41 | { 42 | return await _dbContext.Products 43 | .Where(x => x.CategoryId==categoryId) 44 | .ToListAsync(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Repository/SpecificationEvaluator.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using AspnetRun.Core.Interfaces; 3 | using Microsoft.EntityFrameworkCore; 4 | using System.Linq; 5 | 6 | namespace AspnetRun.Infrastructure.Repository 7 | { 8 | public class SpecificationEvaluator where T : BaseEntity 9 | { 10 | public static IQueryable GetQuery(IQueryable inputQuery, ISpecification specification) 11 | { 12 | var query = inputQuery; 13 | 14 | // modify the IQueryable using the specification's criteria expression 15 | if (specification.Criteria != null) 16 | { 17 | query = query.Where(specification.Criteria); 18 | } 19 | 20 | // Includes all expression-based includes 21 | query = specification.Includes.Aggregate(query, 22 | (current, include) => current.Include(include)); 23 | 24 | // Include any string-based include statements 25 | query = specification.IncludeStrings.Aggregate(query, 26 | (current, include) => current.Include(include)); 27 | 28 | // Apply ordering if expressions are set 29 | if (specification.OrderBy != null) 30 | { 31 | query = query.OrderBy(specification.OrderBy); 32 | } 33 | else if (specification.OrderByDescending != null) 34 | { 35 | query = query.OrderByDescending(specification.OrderByDescending); 36 | } 37 | 38 | // Apply paging if enabled 39 | if (specification.isPagingEnabled) 40 | { 41 | query = query.Skip(specification.Skip) 42 | .Take(specification.Take); 43 | } 44 | return query; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/AspnetRun.Infrastructure/Services/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace AspnetRun.Infrastructure.Services 7 | { 8 | // This class is used by the application to send email for account confirmation and password reset. 9 | // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 10 | public class EmailSender // : IEmailSender 11 | { 12 | public Task SendEmailAsync(string email, string subject, string message) 13 | { 14 | // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. 15 | return Task.CompletedTask; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/AspnetRun.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 7.3 6 | <_RazorComponentInclude>Components\**\*.cshtml 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Components/App.razor: -------------------------------------------------------------------------------- 1 | @* 2 | The Router component displays whichever component has a @page 3 | directive matching the current URI. 4 | *@ 5 | 6 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Components/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | 3 |

Counter

4 | 5 |

Current count: @currentCount

6 | 7 | 8 | 9 | @functions { 10 | int currentCount = 0; 11 | 12 | void IncrementCount() 13 | { 14 | currentCount++; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Components/Pages/FetchData.razor: -------------------------------------------------------------------------------- 1 | @page "/fetchdata" 2 | @using AspnetRun.Web.Services 3 | @inject WeatherForecastService ForecastService 4 | 5 |

Weather forecast

6 | 7 |

This component demonstrates fetching data from a service.

8 | 9 | @if (forecasts == null) 10 | { 11 |

Loading...

12 | } 13 | else 14 | { 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | @foreach (var forecast in forecasts) 26 | { 27 | 28 | 29 | 30 | 31 | 32 | 33 | } 34 | 35 |
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
36 | } 37 | 38 | @functions { 39 | WeatherForecast[] forecasts; 40 | 41 | protected override async Task OnInitAsync() 42 | { 43 | forecasts = await ForecastService.GetForecastAsync(DateTime.Now); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Components/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 |

Hello, world!

4 | 5 | Welcome to your new app. 6 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Components/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @layout MainLayout 2 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Components/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | 6 | 7 |
8 |
9 | About 10 |
11 | 12 |
13 | @Body 14 |
15 |
16 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Components/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | 26 |
27 | 28 | @functions { 29 | bool collapseNavMenu = true; 30 | 31 | string NavMenuCssClass => collapseNavMenu ? "collapse" : null; 32 | 33 | void ToggleNavMenu() 34 | { 35 | collapseNavMenu = !collapseNavMenu; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Components/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Components.Forms 3 | @using Microsoft.AspNetCore.Components.Layouts 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.JSInterop 6 | @using AspnetRun.Web.Components.Shared 7 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page "{*clientPath}" 2 | 3 | 4 | 5 | 6 | 7 | AspnetRun.Web 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | @(await Html.RenderComponentAsync()) 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using AspnetRun.Web.Components 2 | @namespace AspnetRun.Web.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Hosting; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace AspnetRun.Web 13 | { 14 | public class Program 15 | { 16 | public static void Main(string[] args) 17 | { 18 | CreateHostBuilder(args).Build().Run(); 19 | } 20 | 21 | public static IHostBuilder CreateHostBuilder(string[] args) => 22 | Host.CreateDefaultBuilder(args) 23 | .ConfigureWebHostDefaults(webBuilder => 24 | { 25 | webBuilder.UseStartup(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Services/IssueComponentService.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Dtos.Issue; 2 | using AspnetRun.Application.Interfaces; 3 | using AspnetRun.Application.Session; 4 | using AspnetRun.Web.ViewModel; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace AspnetRun.Web.Services 11 | { 12 | public class IssueComponentService 13 | { 14 | private readonly IIssueAppService _issueAppService; 15 | private readonly ISessionService _sessionService; 16 | 17 | public IssueComponentService(IIssueAppService issueAppService, ISessionService sessionService) 18 | { 19 | _issueAppService = issueAppService ?? throw new ArgumentNullException(nameof(issueAppService)); 20 | _sessionService = sessionService ?? throw new ArgumentNullException(nameof(sessionService)); 21 | } 22 | 23 | public void Detail(int id) 24 | { 25 | var output = _issueAppService.GetIssue(new GetIssueInput { Id = id }); 26 | 27 | // TODO : this service will consume by razor components as below way 28 | 29 | //var viewModel = _objectMapper.Map(output); 30 | //viewModel.CurrentUserId = _sessionService.UserId; 31 | 32 | //return View(viewModel); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Services/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspnetRun.Web.Services 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF { get; set; } 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Services/WeatherForecastService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace AspnetRun.Web.Services 6 | { 7 | public class WeatherForecastService 8 | { 9 | private static string[] Summaries = new[] 10 | { 11 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 12 | }; 13 | 14 | public Task GetForecastAsync(DateTime startDate) 15 | { 16 | var rng = new Random(); 17 | return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast 18 | { 19 | Date = startDate.AddDays(index), 20 | TemperatureC = rng.Next(-20, 55), 21 | Summary = Summaries[rng.Next(Summaries.Length)] 22 | }).ToArray()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.HttpsPolicy; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Hosting; 11 | using AspnetRun.Web.Components; 12 | using AspnetRun.Web.Services; 13 | 14 | namespace AspnetRun.Web 15 | { 16 | public class Startup 17 | { 18 | // This method gets called by the runtime. Use this method to add services to the container. 19 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddMvc() 23 | .AddNewtonsoftJson(); 24 | 25 | services.AddRazorComponents(); 26 | 27 | services.AddSingleton(); 28 | } 29 | 30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | else 38 | { 39 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 40 | app.UseHsts(); 41 | } 42 | 43 | app.UseHttpsRedirection(); 44 | app.UseStaticFiles(); 45 | 46 | app.UseRouting(routes => 47 | { 48 | routes.MapRazorPages(); 49 | routes.MapComponentHub("app"); 50 | }); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/ViewModel/IssueDetailViewModel.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Dtos; 2 | using AspnetRun.Application.Dtos.Issue; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace AspnetRun.Web.ViewModel 9 | { 10 | public class IssueDetailViewModel : GetIssueOutput 11 | { 12 | public int CurrentUserId { get; set; } 13 | 14 | public string GetStateText() 15 | { 16 | if (Issue.IsClosed) 17 | { 18 | return "Closed (" + Issue.CloseReason + ")"; 19 | } 20 | else 21 | { 22 | return "Open"; 23 | } 24 | } 25 | 26 | public bool CanDeleteComment(IssueCommentDto comment) 27 | { 28 | return comment.CreatorUser.Id == CurrentUserId; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](http://useiconic.com/open) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetrun/run-aspnetcore-blazor/e061e66120b402153c987ed8d5b933905f050035/src/AspnetRun.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetrun/run-aspnetcore-blazor/e061e66120b402153c987ed8d5b933905f050035/src/AspnetRun.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014 9 | By P.J. Onori 10 | Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) 11 | 12 | 13 | 14 | 27 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 45 | 47 | 49 | 51 | 53 | 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 74 | 76 | 79 | 81 | 84 | 86 | 88 | 91 | 93 | 95 | 98 | 100 | 102 | 104 | 106 | 109 | 112 | 115 | 117 | 121 | 123 | 125 | 127 | 130 | 132 | 134 | 136 | 138 | 141 | 143 | 145 | 147 | 149 | 151 | 153 | 155 | 157 | 159 | 162 | 165 | 167 | 169 | 172 | 174 | 177 | 179 | 181 | 183 | 185 | 189 | 191 | 194 | 196 | 198 | 200 | 202 | 205 | 207 | 209 | 211 | 213 | 215 | 218 | 220 | 222 | 224 | 226 | 228 | 230 | 232 | 234 | 236 | 238 | 241 | 243 | 245 | 247 | 249 | 251 | 253 | 256 | 259 | 261 | 263 | 265 | 267 | 269 | 272 | 274 | 276 | 280 | 282 | 285 | 287 | 289 | 292 | 295 | 298 | 300 | 302 | 304 | 306 | 309 | 312 | 314 | 316 | 318 | 320 | 322 | 324 | 326 | 330 | 334 | 338 | 340 | 343 | 345 | 347 | 349 | 351 | 353 | 355 | 358 | 360 | 363 | 365 | 367 | 369 | 371 | 373 | 375 | 377 | 379 | 381 | 383 | 386 | 388 | 390 | 392 | 394 | 396 | 399 | 401 | 404 | 406 | 408 | 410 | 412 | 414 | 416 | 419 | 421 | 423 | 425 | 428 | 431 | 435 | 438 | 440 | 442 | 444 | 446 | 448 | 451 | 453 | 455 | 457 | 460 | 462 | 464 | 466 | 468 | 471 | 473 | 477 | 479 | 481 | 483 | 486 | 488 | 490 | 492 | 494 | 496 | 499 | 501 | 504 | 506 | 509 | 512 | 515 | 517 | 520 | 522 | 524 | 526 | 529 | 532 | 534 | 536 | 539 | 542 | 543 | 544 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetrun/run-aspnetcore-blazor/e061e66120b402153c987ed8d5b933905f050035/src/AspnetRun.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetrun/run-aspnetcore-blazor/e061e66120b402153c987ed8d5b933905f050035/src/AspnetRun.Web/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | app { 8 | position: relative; 9 | display: flex; 10 | flex-direction: column; 11 | } 12 | 13 | .top-row { 14 | height: 3.5rem; 15 | display: flex; 16 | align-items: center; 17 | } 18 | 19 | .main { 20 | flex: 1; 21 | } 22 | 23 | .main .top-row { 24 | background-color: #e6e6e6; 25 | border-bottom: 1px solid #d6d5d5; 26 | } 27 | 28 | .sidebar { 29 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 30 | } 31 | 32 | .sidebar .top-row { 33 | background-color: rgba(0,0,0,0.4); 34 | } 35 | 36 | .sidebar .navbar-brand { 37 | font-size: 1.1rem; 38 | } 39 | 40 | .sidebar .oi { 41 | width: 2rem; 42 | font-size: 1.1rem; 43 | vertical-align: text-top; 44 | top: -2px; 45 | } 46 | 47 | .nav-item { 48 | font-size: 0.9rem; 49 | padding-bottom: 0.5rem; 50 | } 51 | 52 | .nav-item:first-of-type { 53 | padding-top: 1rem; 54 | } 55 | 56 | .nav-item:last-of-type { 57 | padding-bottom: 1rem; 58 | } 59 | 60 | .nav-item a { 61 | color: #d7d7d7; 62 | border-radius: 4px; 63 | height: 3rem; 64 | display: flex; 65 | align-items: center; 66 | line-height: 3rem; 67 | } 68 | 69 | .nav-item a.active { 70 | background-color: rgba(255,255,255,0.25); 71 | color: white; 72 | } 73 | 74 | .nav-item a:hover { 75 | background-color: rgba(255,255,255,0.1); 76 | color: white; 77 | } 78 | 79 | .content { 80 | padding-top: 1.1rem; 81 | } 82 | 83 | .navbar-toggler { 84 | background-color: rgba(255, 255, 255, 0.1); 85 | } 86 | 87 | .valid.modified:not([type=checkbox]) { 88 | outline: 1px solid #26b050; 89 | } 90 | 91 | .invalid { 92 | outline: 1px solid red; 93 | } 94 | 95 | .validation-message { 96 | color: red; 97 | } 98 | 99 | @media (max-width: 767.98px) { 100 | .main .top-row { 101 | display: none; 102 | } 103 | } 104 | 105 | @media (min-width: 768px) { 106 | app { 107 | flex-direction: row; 108 | } 109 | 110 | .sidebar { 111 | width: 250px; 112 | height: 100vh; 113 | position: sticky; 114 | top: 0; 115 | } 116 | 117 | .main .top-row { 118 | position: sticky; 119 | top: 0; 120 | } 121 | 122 | .main > div { 123 | padding-left: 2rem !important; 124 | padding-right: 1.5rem !important; 125 | } 126 | 127 | .navbar-toggler { 128 | display: none; 129 | } 130 | 131 | .sidebar .collapse { 132 | /* Never collapse the sidebar for wide screens */ 133 | display: block; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/AspnetRun.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetrun/run-aspnetcore-blazor/e061e66120b402153c987ed8d5b933905f050035/src/AspnetRun.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /test/AspnetRun.Application.Tests/AspnetRun.Application.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/AspnetRun.Application.Tests/Services/DeleteProduct.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Application.Services; 2 | using AspnetRun.Core.Entities; 3 | using AspnetRun.Core.Interfaces; 4 | using Moq; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace AspnetRun.Application.Tests.Services 12 | { 13 | public class DeleteProduct 14 | { 15 | private Mock _mockProductRepository; 16 | private Mock> _mockCategoryRepository; 17 | 18 | 19 | public DeleteProduct() 20 | { 21 | _mockProductRepository = new Mock(); 22 | _mockCategoryRepository = new Mock>(); 23 | } 24 | 25 | //[Fact] 26 | //public async Task Should_GetOnce_WhenAddedTwoProduct() 27 | //{ 28 | // var category = Category.Create(It.IsAny(), It.IsAny()); 29 | // var product1 = Product.Create(It.IsAny(), category.Id, It.IsAny()); 30 | // var product2 = Product.Create(It.IsAny(), category.Id, It.IsAny()); 31 | 32 | // category.AddProduct(product1.Id, It.IsAny()); 33 | // category.AddProduct(product2.Id, It.IsAny()); 34 | 35 | // _mockCategoryRepository.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(category); 36 | // _mockProductRepository.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(product1); 37 | // _mockProductRepository.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(product2); 38 | 39 | // var productService = new ProductAppService(_mockProductRepository.Object); 40 | // var productList = productService.GetProductList(); 41 | 42 | // _mockProductRepository.Verify(x => x.GetAllAsync(), Times.Once); 43 | //} 44 | 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/AspnetRun.Core.Tests/AspnetRun.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/AspnetRun.Core.Tests/Entities/CategoryAddProduct.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace AspnetRun.Core.Tests.Entities 9 | { 10 | public class CategoryAddProduct 11 | { 12 | private int _testProductId = 3; 13 | private int _testCategoryId = 5; 14 | private string _testProductName = "Reason"; 15 | private decimal _testUnitPrice = 1.23m; 16 | private short _testQuantity = 2; 17 | 18 | [Fact] 19 | public void AddsProductIfNotPresent() 20 | { 21 | var category = Category.Create(_testCategoryId, "newCategory"); 22 | category.AddProduct(_testProductId, _testProductName); 23 | 24 | var firstItem = category.Products.Single(); 25 | Assert.Equal(_testCategoryId, category.Id); 26 | Assert.Equal(_testProductId, firstItem.Id); 27 | } 28 | 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/AspnetRun.Core.Tests/Entities/ProductCreate.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace AspnetRun.Core.Tests.Entities 8 | { 9 | public class ProductCreate 10 | { 11 | private int _testProductId = 2; 12 | private int _testCategoryId = 3; 13 | private string _testProductName = "Reason"; 14 | private decimal _testUnitPrice = 1.23m; 15 | private short _testQuantity = 2; 16 | 17 | [Fact] 18 | public void CreateProductIfNotPresent() 19 | { 20 | var product = Product.Create(_testProductId, _testCategoryId, _testProductName, _testUnitPrice, _testQuantity, null, null, false); 21 | 22 | Assert.Equal(_testProductId, product.Id); 23 | Assert.Equal(_testCategoryId, product.CategoryId); 24 | Assert.Equal(_testProductName, product.ProductName); 25 | Assert.Equal(_testUnitPrice, product.UnitPrice); 26 | Assert.Equal(_testQuantity, product.UnitsInStock); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/AspnetRun.Core.Tests/ProductWithCategoryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace AspnetRun.Core.Tests 5 | { 6 | public class ProductWithCategoryBuilder 7 | { 8 | public ProductWithCategoryBuilder() 9 | { 10 | 11 | } 12 | 13 | [Fact] 14 | public void Test1() 15 | { 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/AspnetRun.Infrastructure.Tests/AspnetRun.Infrastructure.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/AspnetRun.Infrastructure.Tests/Builders/ProductBuilder.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace AspnetRun.Infrastructure.Tests.Builders 7 | { 8 | public class ProductBuilder 9 | { 10 | private Product _product; 11 | public int TestProductId { get; } = 5; 12 | public string TestProductName => "Test Product Name"; 13 | public int TestCategoryId { get; } = 8; 14 | 15 | public decimal TestUnitPrice = 1.23m; 16 | public short TestUnitInStock = 4; 17 | public short TestUnitsOnOrder = 4; 18 | public short ReOrderLevel = 4; 19 | public bool Discontinued = true; 20 | 21 | public ProductBuilder() 22 | { 23 | _product = WithDefaultValues(); 24 | 25 | } 26 | 27 | public Product Build() 28 | { 29 | return _product; 30 | } 31 | 32 | public Product WithDefaultValues() 33 | { 34 | return Product.Create(TestProductId, TestCategoryId, TestProductName); 35 | } 36 | 37 | public Product WithAllValues() 38 | { 39 | return Product.Create(TestProductId, TestCategoryId, TestProductName, TestUnitPrice, TestUnitInStock, TestUnitsOnOrder, ReOrderLevel, Discontinued); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/AspnetRun.Infrastructure.Tests/Repositories/ProductGetById.cs: -------------------------------------------------------------------------------- 1 | using AspnetRun.Infrastructure.Data; 2 | using AspnetRun.Infrastructure.Repository; 3 | using AspnetRun.Infrastructure.Tests.Builders; 4 | using Microsoft.EntityFrameworkCore; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | 12 | namespace AspnetRun.Infrastructure.Tests.Repositories 13 | { 14 | public class ProductGetById 15 | { 16 | private readonly AspnetRunContext _aspnetRunContext; 17 | private readonly ProductRepository _productRepository; 18 | private ProductBuilder ProductBuilder { get; } = new ProductBuilder(); 19 | 20 | private readonly ITestOutputHelper _output; 21 | public ProductGetById(ITestOutputHelper output) 22 | { 23 | _output = output; 24 | var dbOptions = new DbContextOptionsBuilder() 25 | .UseInMemoryDatabase(databaseName: "TestProduct") 26 | .Options; 27 | _aspnetRunContext = new AspnetRunContext(dbOptions); 28 | _productRepository = new ProductRepository(_aspnetRunContext); 29 | } 30 | 31 | [Fact] 32 | public async Task GetsExistingProduct() 33 | { 34 | var existingProduct = ProductBuilder.WithDefaultValues(); 35 | _aspnetRunContext.Products.Add(existingProduct); 36 | _aspnetRunContext.SaveChanges(); 37 | var productId = existingProduct.Id; 38 | _output.WriteLine($"ProductId: {productId}"); 39 | 40 | var productFromRepo = await _productRepository.GetByIdAsync(productId); 41 | Assert.Equal(ProductBuilder.TestProductId, productFromRepo.Id); 42 | Assert.Equal(ProductBuilder.TestCategoryId, productFromRepo.CategoryId); 43 | } 44 | 45 | } 46 | } 47 | --------------------------------------------------------------------------------