├── .dockerignore ├── .gitignore ├── Code-of-Conduct.md ├── Dockerfile ├── LICENSE ├── NuGetReadme.md ├── Onion.Data ├── Entities │ ├── BaseAuditClass.cs │ ├── Book.cs │ ├── BookSeries.cs │ └── Series.cs └── Onion.Data.csproj ├── Onion.Repo ├── ConfigurationBase.cs ├── Data │ ├── DataContext.cs │ ├── DataRepository.cs │ ├── IRepository.cs │ ├── Mapping │ │ ├── BookMap.cs │ │ └── SeriesMap.cs │ └── Migrations │ │ ├── 20170924124322_InitialMigration.Designer.cs │ │ ├── 20170924124322_InitialMigration.cs │ │ └── DataContextModelSnapshot.cs ├── DatabaseConfiguration.cs ├── DbContextFactory.cs ├── Extentions │ ├── ChangeTrackerExtentions.cs │ └── DbContextExtensions.cs ├── Helpers │ └── DatabaseSeeder.cs ├── Identity │ ├── AppIdentityDbContext.cs │ ├── ApplicationUser.cs │ └── Migrations │ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs │ │ ├── 00000000000000_CreateIdentitySchema.cs │ │ └── ApplicationDbContextModelSnapshot.cs └── Onion.Repo.csproj ├── Onion.Service ├── BookSeriesService.cs ├── BookService.cs ├── EmailSender.cs ├── Interfaces │ ├── IBookSeriesService.cs │ ├── IBookService.cs │ ├── IEmailSender.cs │ └── ISeriesService.cs ├── Onion.Service.csproj └── SeriesService.cs ├── Onion.Web ├── ConfigureContainerExtentions.cs ├── Dockerfile ├── Extensions │ ├── EmailSenderExtensions.cs │ └── UrlHelperExtensions.cs ├── FeatureLocationExpander.cs ├── Features │ ├── Account │ │ ├── AccessDenied.cshtml │ │ ├── AccountController.cs │ │ ├── AccountViewModels │ │ │ ├── ExternalLoginViewModel.cs │ │ │ ├── ForgotPasswordViewModel.cs │ │ │ ├── LoginViewModel.cs │ │ │ ├── LoginWith2faViewModel.cs │ │ │ ├── LoginWithRecoveryCodeViewModel.cs │ │ │ ├── RegisterViewModel.cs │ │ │ └── ResetPasswordViewModel.cs │ │ ├── ConfirmEmail.cshtml │ │ ├── ExternalLogin.cshtml │ │ ├── ForgotPassword.cshtml │ │ ├── ForgotPasswordConfirmation.cshtml │ │ ├── Lockout.cshtml │ │ ├── Login.cshtml │ │ ├── LoginWith2fa.cshtml │ │ ├── LoginWithRecoveryCode.cshtml │ │ ├── Register.cshtml │ │ ├── ResetPassword.cshtml │ │ ├── ResetPasswordConfirmation.cshtml │ │ └── SignedOut.cshtml │ ├── ErrorViewModel.cs │ ├── Home │ │ ├── About.cshtml │ │ ├── Contact.cshtml │ │ ├── HomeController.cs │ │ └── Index.cshtml │ ├── Manage │ │ ├── ChangePassword.cshtml │ │ ├── Disable2fa.cshtml │ │ ├── EnableAuthenticator.cshtml │ │ ├── ExternalLogins.cshtml │ │ ├── GenerateRecoveryCodes.cshtml │ │ ├── Index.cshtml │ │ ├── ManageController.cs │ │ ├── ManageNavPages.cs │ │ ├── ResetAuthenticator.cshtml │ │ ├── SetPassword.cshtml │ │ ├── TwoFactorAuthentication.cshtml │ │ ├── ViewModels │ │ │ ├── ChangePasswordViewModel.cs │ │ │ ├── EnableAuthenticatorViewModel.cs │ │ │ ├── ExternalLoginsViewModel.cs │ │ │ ├── GenerateRecoveryCodesViewModel.cs │ │ │ ├── IndexViewModel.cs │ │ │ ├── RemoveLoginViewModel.cs │ │ │ ├── SetPasswordViewModel.cs │ │ │ └── TwoFactorAuthenticationViewModel.cs │ │ ├── _Layout.cshtml │ │ ├── _ManageNav.cshtml │ │ ├── _StatusMessage.cshtml │ │ └── _ViewImports.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _Layout.cshtml │ │ ├── _LoginPartial.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── Onion.Web.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json ├── bundleconfig.json ├── package-lock.json ├── package.json ├── webpack.assets.js ├── webpack.config.js └── wwwroot │ ├── app.bundle.js │ ├── css │ ├── site.css │ └── site.min.css │ ├── favicon.ico │ ├── images │ ├── banner1.svg │ ├── banner2.svg │ ├── banner3.svg │ └── banner4.svg │ ├── js │ ├── site.js │ └── site.min.js │ └── lib │ ├── bootstrap │ ├── .bower.json │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap-theme.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js │ ├── jquery-validation-unobtrusive │ ├── .bower.json │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── .bower.json │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── .bower.json │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── OnionArch.nuspec ├── OnionArch.sln ├── README.md ├── appveyor.yml ├── commands.md ├── logo.svg ├── onionArch-nuget-logo.png ├── onionArch.png └── template.config └── template.json /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | .vscode/ 25 | *.nupkg 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # DNX 46 | project.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | *.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | nupkgs/* 158 | # The packages folder can be ignored because of Package Restore 159 | **/packages/* 160 | # except build/, which is used as an MSBuild target. 161 | !**/packages/build/ 162 | # Uncomment if necessary however generally it will be regenerated when needed 163 | #!**/packages/repositories.config 164 | # NuGet v3's project.json files produces more ignoreable files 165 | *.nuget.props 166 | *.nuget.targets 167 | 168 | # Microsoft Azure Build Output 169 | csx/ 170 | *.build.csdef 171 | 172 | # Microsoft Azure Emulator 173 | ecf/ 174 | rcf/ 175 | 176 | # Windows Store app package directories and files 177 | AppPackages/ 178 | BundleArtifacts/ 179 | Package.StoreAssociation.xml 180 | _pkginfo.txt 181 | 182 | # Visual Studio cache files 183 | # files ending in .cache can be ignored 184 | *.[Cc]ache 185 | # but keep track of directories ending in .cache 186 | !*.[Cc]ache/ 187 | 188 | # Others 189 | ClientBin/ 190 | ~$* 191 | *~ 192 | *.dbmdl 193 | *.dbproj.schemaview 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # Mac OS chaff 258 | .DS_Store 259 | 260 | # Sqlite databases 261 | *.db 262 | 263 | .idea/ 264 | -------------------------------------------------------------------------------- /Code-of-Conduct.md: -------------------------------------------------------------------------------- 1 | This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from [http://contributor-covenant.org/version/1/3/0/](http://contributor-covenant.org/version/1/3/0/) 2 | 3 | # Contributor Code of Conduct 4 | 5 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 6 | 7 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 8 | 9 | Examples of unacceptable behavior by participants include: 10 | 11 | - The use of sexualized language or imagery 12 | - Personal attacks 13 | -Trolling or insulting/derogatory comments 14 | -Public or private harassment 15 | -Publishing other's private information, such as physical or electronic addresses, without explicit permission 16 | - Other unethical or unprofessional conduct 17 | -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 18 | 19 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 20 | 21 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 22 | 23 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at jamiegaprogmancom. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. 24 | 25 | This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from [http://contributor-covenant.org/version/1/3/0/](http://contributor-covenant.org/version/1/3/0/) -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:sdk AS publish 2 | 3 | RUN mkdir /app 4 | RUN mkdir /app/out 5 | 6 | WORKDIR /app 7 | COPY . ./ 8 | 9 | RUN dotnet publish \ 10 | --configuration release \ 11 | --output /app/out \ 12 | /app/Onion.Web/Onion.Web.csproj 13 | 14 | FROM microsoft/dotnet:aspnetcore-runtime AS deploy 15 | 16 | RUN mkdir /app 17 | WORKDIR /app 18 | 19 | COPY --from=publish /app/out /app 20 | 21 | CMD [ "dotnet", "/app/Onion.Web.dll" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jamie Taylor 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 | -------------------------------------------------------------------------------- /NuGetReadme.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | OnionArch.Mvc is an ASP.NET Core project template which uses the onion architecture and domain driven design to scaffold an ASP.NET Core MVC application using feature folders. 4 | 5 | ## Installation 6 | 7 | To install this project template, use the following command: 8 | 9 | dotnet new --install OnionArch.Mvc 10 | 11 | ## Usage 12 | 13 | Issuing the following command will create a new project with the name `testProject`: 14 | 15 | dotnet new onionArch --name testProject 16 | 17 | OnionArch.Mvc also has a number of parameterised command line switches, details on these can be displayed by running the following command: 18 | 19 | dotnet new onionArch --help 20 | 21 | The output of which should look similar to the following: 22 | 23 | Onion Architecture MVC (C#) 24 | Author: Jamie Taylor 25 | Options: 26 | -egp|--enable-gnu-pratchett Whether to include and activate middleware which will include the X-GNU-Pratchett header in all requests 27 | bool - Optional 28 | Default: false / (*) true 29 | 30 | -esh|--enable-secure-headers Whether to include and activate middleware which will include a range of OWASP suggested security headers 31 | bool - Optional 32 | Default: false / (*) true -------------------------------------------------------------------------------- /Onion.Data/Entities/BaseAuditClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Onion.Data.Entities 4 | { 5 | public abstract class BaseAuditClass 6 | { 7 | public int Id { get; set; } 8 | public DateTime Created { get; set; } 9 | public DateTime Modified { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Onion.Data/Entities/Book.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace Onion.Data.Entities 5 | { 6 | public class Book : BaseAuditClass 7 | { 8 | public int BookOrdinal { get; set; } 9 | public string BookName { get; set; } 10 | public string BookIsbn10 { get; set; } 11 | public string BookIsbn13 { get; set; } 12 | public string BookDescription { get; set; } 13 | public byte[] BookCoverImage { get; set; } 14 | public string BookCoverImageUrl { get; set; } 15 | 16 | public virtual ICollection BookSeries { get; } = new Collection(); 17 | } 18 | } -------------------------------------------------------------------------------- /Onion.Data/Entities/BookSeries.cs: -------------------------------------------------------------------------------- 1 | namespace Onion.Data.Entities 2 | { 3 | public class BookSeries : BaseAuditClass 4 | { 5 | public int BookId { get; set; } 6 | public virtual Book Book { get; set; } 7 | 8 | public int SeriesId { get; set; } 9 | public virtual Series Series { get; set; } 10 | 11 | public int Ordinal { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Onion.Data/Entities/Series.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace Onion.Data.Entities 5 | { 6 | public class Series : BaseAuditClass 7 | { 8 | public string SeriesName { get; set; } 9 | 10 | public virtual ICollection BookSeries { get; set; } = new Collection(); 11 | } 12 | } -------------------------------------------------------------------------------- /Onion.Data/Onion.Data.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | -------------------------------------------------------------------------------- /Onion.Repo/ConfigurationBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | 4 | namespace Onion.Repo 5 | { 6 | public abstract class ConfigurationBase 7 | { 8 | protected IConfigurationRoot GetConfiguration() 9 | { 10 | return new ConfigurationBuilder() 11 | .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) 12 | .AddJsonFile("appsettings.json") 13 | .Build(); 14 | } 15 | 16 | protected void RaiseValueNotFoundException(string configurationKey) 17 | { 18 | throw new Exception($"appsettings key ({configurationKey}) could not be found."); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Onion.Repo/Data/DataContext.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.EntityFrameworkCore; 4 | using Onion.Data.Entities; 5 | using Onion.Repo.Data.Mapping; 6 | using Onion.Repo.Extentions; 7 | 8 | namespace Onion.Repo.Data 9 | { 10 | public class DataContext : DbContext 11 | { 12 | public DataContext(DbContextOptions options) : base(options) 13 | { 14 | 15 | } 16 | 17 | protected override void OnModelCreating(ModelBuilder modelBuilder) 18 | { 19 | var bookMap = new BookMap(modelBuilder.Entity()); 20 | 21 | base.OnModelCreating(modelBuilder); 22 | } 23 | 24 | public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, 25 | CancellationToken cancellationToken = default(CancellationToken)) 26 | { 27 | ChangeTracker.ApplyAuditInformation(); 28 | 29 | return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); 30 | } 31 | 32 | public DbSet Books { get; set; } 33 | public DbSet Series { get; set; } 34 | public DbSet BookSeries { get; set; } 35 | } 36 | } -------------------------------------------------------------------------------- /Onion.Repo/Data/DataRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.EntityFrameworkCore; 5 | using Onion.Data.Entities; 6 | 7 | namespace Onion.Repo.Data 8 | { 9 | public class DataRepository : IRepository where T : BaseAuditClass 10 | { 11 | private readonly DataContext context; 12 | private DbSet entities; 13 | string errorMessage = string.Empty; 14 | 15 | public DataRepository(DataContext context) 16 | { 17 | this.context = context; 18 | entities = context.Set(); 19 | } 20 | public IEnumerable GetAll() 21 | { 22 | return entities.AsEnumerable(); 23 | } 24 | 25 | public T Get(int id) 26 | { 27 | return entities.SingleOrDefault(s => s.Id == id); 28 | } 29 | public void Insert(T entity) 30 | { 31 | if (entity == null) 32 | { 33 | throw new ArgumentNullException("entity"); 34 | } 35 | entities.Add(entity); 36 | context.SaveChanges(); 37 | } 38 | 39 | public void Update(T entity) 40 | { 41 | if (entity == null) 42 | { 43 | throw new ArgumentNullException("entity"); 44 | } 45 | context.SaveChanges(); 46 | } 47 | 48 | public void Delete(T entity) 49 | { 50 | if (entity == null) 51 | { 52 | throw new ArgumentNullException("entity"); 53 | } 54 | entities.Remove(entity); 55 | context.SaveChanges(); 56 | } 57 | public void Remove(T entity) 58 | { 59 | if (entity == null) 60 | { 61 | throw new ArgumentNullException("entity"); 62 | } 63 | entities.Remove(entity); 64 | } 65 | 66 | public void SaveChanges() 67 | { 68 | context.SaveChanges(); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Onion.Repo/Data/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Onion.Data.Entities; 3 | 4 | namespace Onion.Repo.Data 5 | { 6 | public interface IRepository where T : BaseAuditClass 7 | { 8 | IEnumerable GetAll(); 9 | T Get(int id); 10 | void Insert(T entity); 11 | void Update(T entity); 12 | void Delete(T entity); 13 | void Remove(T entity); 14 | void SaveChanges(); 15 | } 16 | } -------------------------------------------------------------------------------- /Onion.Repo/Data/Mapping/BookMap.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Onion.Data.Entities; 4 | 5 | namespace Onion.Repo.Data.Mapping 6 | { 7 | public class BookMap 8 | { 9 | public BookMap(EntityTypeBuilder entityBuilder) 10 | { 11 | entityBuilder.HasKey(b => b.Id); 12 | entityBuilder.HasMany(b => b.BookSeries) 13 | .WithOne(bs => bs.Book) 14 | .HasForeignKey(bs => bs.BookId); 15 | entityBuilder.ToTable("Books"); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Onion.Repo/Data/Mapping/SeriesMap.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using Onion.Data.Entities; 4 | 5 | namespace Onion.Repo.Data.Mapping 6 | { 7 | public class SeriesMap 8 | { 9 | public SeriesMap(EntityTypeBuilder entityBuilder) 10 | { 11 | entityBuilder.HasKey(s => s.Id); 12 | entityBuilder.HasMany(s => s.BookSeries) 13 | .WithOne(bs => bs.Series) 14 | .HasForeignKey(bs => bs.BookId); 15 | entityBuilder.ToTable("Series"); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Onion.Repo/Data/Migrations/20170924124322_InitialMigration.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage; 7 | using Microsoft.EntityFrameworkCore.Storage.Internal; 8 | using Onion.Repo.Data; 9 | using System; 10 | 11 | namespace Onion.Repo.Data.Migrations 12 | { 13 | [DbContext(typeof(DataContext))] 14 | [Migration("20170924124322_InitialMigration")] 15 | partial class InitialMigration 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); 22 | 23 | modelBuilder.Entity("Onion.Data.Book", b => 24 | { 25 | b.Property("Id") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("Created"); 29 | 30 | b.Property("Description"); 31 | 32 | b.Property("Modified"); 33 | 34 | b.Property("Name"); 35 | 36 | b.Property("Ordinal"); 37 | 38 | b.HasKey("Id"); 39 | 40 | b.ToTable("Books"); 41 | }); 42 | #pragma warning restore 612, 618 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Onion.Repo/Data/Migrations/20170924124322_InitialMigration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | using System; 3 | 4 | namespace Onion.Repo.Data.Migrations 5 | { 6 | public partial class InitialMigration : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "Books", 12 | columns: table => new 13 | { 14 | Id = table.Column(type: "INTEGER", nullable: false) 15 | .Annotation("Sqlite:Autoincrement", true), 16 | Created = table.Column(type: "TEXT", nullable: false), 17 | Description = table.Column(type: "TEXT", nullable: true), 18 | Modified = table.Column(type: "TEXT", nullable: false), 19 | Name = table.Column(type: "TEXT", nullable: true), 20 | Ordinal = table.Column(type: "INTEGER", nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_Books", x => x.Id); 25 | }); 26 | } 27 | 28 | protected override void Down(MigrationBuilder migrationBuilder) 29 | { 30 | migrationBuilder.DropTable( 31 | name: "Books"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Onion.Repo/Data/Migrations/DataContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using System; 5 | 6 | namespace Onion.Repo.Data.Migrations 7 | { 8 | [DbContext(typeof(DataContext))] 9 | partial class DataContextModelSnapshot : ModelSnapshot 10 | { 11 | protected override void BuildModel(ModelBuilder modelBuilder) 12 | { 13 | #pragma warning disable 612, 618 14 | modelBuilder 15 | .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); 16 | 17 | modelBuilder.Entity("Onion.Data.Book", b => 18 | { 19 | b.Property("Id") 20 | .ValueGeneratedOnAdd(); 21 | 22 | b.Property("Created"); 23 | 24 | b.Property("Description"); 25 | 26 | b.Property("Modified"); 27 | 28 | b.Property("Name"); 29 | 30 | b.Property("Ordinal"); 31 | 32 | b.HasKey("Id"); 33 | 34 | b.ToTable("Books"); 35 | }); 36 | #pragma warning restore 612, 618 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Onion.Repo/DatabaseConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Onion.Repo 4 | { 5 | public class DatabaseConfiguration : ConfigurationBase 6 | { 7 | private string DataConnectionKey = "onionDataConnection"; 8 | 9 | private string AuthConnectionKey = "onionAuthConnection"; 10 | 11 | public string GetDataConnectionString() 12 | { 13 | return GetConfiguration().GetConnectionString(DataConnectionKey); 14 | } 15 | 16 | public string GetAuthConnectionString() 17 | { 18 | return GetConfiguration().GetConnectionString(AuthConnectionKey); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Onion.Repo/DbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Design; 3 | using Onion.Repo.Data; 4 | 5 | namespace Onion.Repo 6 | { 7 | /// 8 | /// This factory is provided so that the EF Core tools can build a full context 9 | /// without having to have access to where the DbContext is being created (i.e. 10 | /// in the UI layer). 11 | /// 12 | /// 13 | /// Please see the following URL for more information: 14 | /// https://docs.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext#using-idbcontextfactorytcontext 15 | /// 16 | public class DbContextFactory : IDesignTimeDbContextFactory 17 | { 18 | private static string DataConnectionString => new DatabaseConfiguration().GetDataConnectionString(); 19 | 20 | public DataContext CreateDbContext(string[] args) 21 | { 22 | var optionsBuilder = new DbContextOptionsBuilder(); 23 | 24 | optionsBuilder.UseSqlite(DataConnectionString); 25 | 26 | return new DataContext(optionsBuilder.Options); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Onion.Repo/Extentions/ChangeTrackerExtentions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.ChangeTracking; 4 | using Onion.Data.Entities; 5 | 6 | namespace Onion.Repo.Extentions 7 | { 8 | public static class ChangeTrackerExtensions 9 | { 10 | public static void ApplyAuditInformation(this ChangeTracker changeTracker) 11 | { 12 | foreach (var entry in changeTracker.Entries()) 13 | { 14 | if (!(entry.Entity is BaseAuditClass baseAudit)) continue; 15 | 16 | var now = DateTime.UtcNow; 17 | switch (entry.State) 18 | { 19 | case EntityState.Modified: 20 | baseAudit.Created = now; 21 | baseAudit.Modified = now; 22 | break; 23 | 24 | case EntityState.Added: 25 | baseAudit.Created = now; 26 | break; 27 | } 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Onion.Repo/Extentions/DbContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Onion.Repo.Data; 3 | using Onion.Repo.Helpers; 4 | 5 | namespace Onion.Repo.Extentions 6 | { 7 | public static class DbContextExtensions 8 | { 9 | public static int EnsureSeedData(this DataContext appContext) 10 | { 11 | var bookCount = default(int); 12 | var seriesCount = default(int); 13 | var bookSeriesCount = default(int); 14 | 15 | var dbSeeder = new DatabaseSeeder(appContext); 16 | 17 | if (!appContext.Books.Any()) 18 | { 19 | bookCount = dbSeeder.SeedBookEntries().Result; 20 | } 21 | 22 | if (!appContext.Series.Any()) 23 | { 24 | seriesCount = dbSeeder.SeedSeriesEntries().Result; 25 | } 26 | 27 | if (!appContext.BookSeries.Any()) 28 | { 29 | bookSeriesCount = dbSeeder.SeedBookSeriesEntries().Result; 30 | } 31 | 32 | return bookCount + seriesCount + bookSeriesCount; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Onion.Repo/Helpers/DatabaseSeeder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Onion.Data.Entities; 5 | using Onion.Repo.Data; 6 | 7 | namespace Onion.Repo.Helpers 8 | { 9 | public class DatabaseSeeder 10 | { 11 | private readonly DataContext _dataContext; 12 | 13 | public DatabaseSeeder(DataContext dataContext) 14 | { 15 | _dataContext = dataContext; 16 | } 17 | 18 | public async Task SeedBookEntries() 19 | { 20 | _dataContext.Books.Add( 21 | new Book() 22 | { 23 | BookName = "The Colour of Magic", 24 | BookOrdinal = 1, 25 | BookIsbn10 = "086140324X", 26 | BookIsbn13 = "9780552138932", 27 | BookDescription = "On a world supported on the back of a giant turtle (sex unknown), a gleeful, explosive, wickedly eccentric expedition sets out. There's an avaricious but inept wizard, a naive tourist whose luggage moves on hundreds of dear little legs, dragons who only exist if you believe in them, and of course THE EDGE of the planet ...", 28 | BookCoverImageUrl = "http://wiki.lspace.org/mediawiki/images/c/c9/Cover_The_Colour_Of_Magic.jpg" 29 | }); 30 | // TODO implement method 31 | return await _dataContext.SaveChangesAsync(); 32 | } 33 | 34 | public async Task SeedSeriesEntries() 35 | { 36 | _dataContext.Series.Add(new Series() 37 | { 38 | SeriesName = "Rincewind" 39 | }); 40 | // TODO implement method 41 | return await _dataContext.SaveChangesAsync(); 42 | } 43 | 44 | public async Task SeedBookSeriesEntries() 45 | { 46 | var rincewindBooks = 47 | _dataContext.Books.Where(book => book.BookName == "The Colour of Magic"); 48 | 49 | var rincewindSeries = _dataContext.Series.FirstOrDefault(series => series.SeriesName == "Rincewind"); 50 | 51 | if (!rincewindBooks.Any() || rincewindSeries == null) 52 | { 53 | throw new ArgumentException($"Unable to see {nameof(BookSeries)}"); 54 | } 55 | 56 | _dataContext.BookSeries.AddRange(rincewindBooks.Select(b => new BookSeries() 57 | { 58 | BookId = b.Id, 59 | SeriesId = rincewindSeries.Id 60 | })); 61 | 62 | // TODO implement method 63 | return await _dataContext.SaveChangesAsync(); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Onion.Repo/Identity/AppIdentityDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace Onion.Repo.Identity 5 | { 6 | public class AppIdentityDbContext : IdentityDbContext 7 | { 8 | public AppIdentityDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | 13 | protected override void OnModelCreating(ModelBuilder builder) 14 | { 15 | base.OnModelCreating(builder); 16 | // Customize the ASP.NET Identity model and override the defaults if needed. 17 | // For example, you can rename the ASP.NET Identity table names and more. 18 | // Add your customizations after calling base.OnModelCreating(builder); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Onion.Repo/Identity/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace Onion.Repo.Identity 4 | { 5 | // Add profile data for application users by adding properties to the ApplicationUser class 6 | public class ApplicationUser : IdentityUser 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Onion.Repo/Identity/Migrations/00000000000000_CreateIdentitySchema.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Migrations; 5 | 6 | namespace Onion.Repo.Identity.Migrations 7 | { 8 | [DbContext(typeof(AppIdentityDbContext))] 9 | [Migration("00000000000000_CreateIdentitySchema")] 10 | partial class CreateIdentitySchema 11 | { 12 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 13 | { 14 | modelBuilder 15 | .HasAnnotation("ProductVersion", "1.0.2"); 16 | 17 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 18 | { 19 | b.Property("Id"); 20 | 21 | b.Property("ConcurrencyStamp") 22 | .IsConcurrencyToken(); 23 | 24 | b.Property("Name") 25 | .HasMaxLength(256); 26 | 27 | b.Property("NormalizedName") 28 | .HasMaxLength(256); 29 | 30 | b.HasKey("Id"); 31 | 32 | b.HasIndex("NormalizedName") 33 | .HasName("RoleNameIndex"); 34 | 35 | b.ToTable("AspNetRoles"); 36 | }); 37 | 38 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 39 | { 40 | b.Property("Id") 41 | .ValueGeneratedOnAdd(); 42 | 43 | b.Property("ClaimType"); 44 | 45 | b.Property("ClaimValue"); 46 | 47 | b.Property("RoleId") 48 | .IsRequired(); 49 | 50 | b.HasKey("Id"); 51 | 52 | b.HasIndex("RoleId"); 53 | 54 | b.ToTable("AspNetRoleClaims"); 55 | }); 56 | 57 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 58 | { 59 | b.Property("Id") 60 | .ValueGeneratedOnAdd(); 61 | 62 | b.Property("ClaimType"); 63 | 64 | b.Property("ClaimValue"); 65 | 66 | b.Property("UserId") 67 | .IsRequired(); 68 | 69 | b.HasKey("Id"); 70 | 71 | b.HasIndex("UserId"); 72 | 73 | b.ToTable("AspNetUserClaims"); 74 | }); 75 | 76 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 77 | { 78 | b.Property("LoginProvider"); 79 | 80 | b.Property("ProviderKey"); 81 | 82 | b.Property("ProviderDisplayName"); 83 | 84 | b.Property("UserId") 85 | .IsRequired(); 86 | 87 | b.HasKey("LoginProvider", "ProviderKey"); 88 | 89 | b.HasIndex("UserId"); 90 | 91 | b.ToTable("AspNetUserLogins"); 92 | }); 93 | 94 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 95 | { 96 | b.Property("UserId"); 97 | 98 | b.Property("RoleId"); 99 | 100 | b.HasKey("UserId", "RoleId"); 101 | 102 | b.HasIndex("RoleId"); 103 | 104 | b.HasIndex("UserId"); 105 | 106 | b.ToTable("AspNetUserRoles"); 107 | }); 108 | 109 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 110 | { 111 | b.Property("UserId"); 112 | 113 | b.Property("LoginProvider"); 114 | 115 | b.Property("Name"); 116 | 117 | b.Property("Value"); 118 | 119 | b.HasKey("UserId", "LoginProvider", "Name"); 120 | 121 | b.ToTable("AspNetUserTokens"); 122 | }); 123 | 124 | modelBuilder.Entity("Onion.Web.Models.ApplicationUser", b => 125 | { 126 | b.Property("Id"); 127 | 128 | b.Property("AccessFailedCount"); 129 | 130 | b.Property("ConcurrencyStamp") 131 | .IsConcurrencyToken(); 132 | 133 | b.Property("Email") 134 | .HasMaxLength(256); 135 | 136 | b.Property("EmailConfirmed"); 137 | 138 | b.Property("LockoutEnabled"); 139 | 140 | b.Property("LockoutEnd"); 141 | 142 | b.Property("NormalizedEmail") 143 | .HasMaxLength(256); 144 | 145 | b.Property("NormalizedUserName") 146 | .HasMaxLength(256); 147 | 148 | b.Property("PasswordHash"); 149 | 150 | b.Property("PhoneNumber"); 151 | 152 | b.Property("PhoneNumberConfirmed"); 153 | 154 | b.Property("SecurityStamp"); 155 | 156 | b.Property("TwoFactorEnabled"); 157 | 158 | b.Property("UserName") 159 | .HasMaxLength(256); 160 | 161 | b.HasKey("Id"); 162 | 163 | b.HasIndex("NormalizedEmail") 164 | .HasName("EmailIndex"); 165 | 166 | b.HasIndex("NormalizedUserName") 167 | .IsUnique() 168 | .HasName("UserNameIndex"); 169 | 170 | b.ToTable("AspNetUsers"); 171 | }); 172 | 173 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 174 | { 175 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 176 | .WithMany("Claims") 177 | .HasForeignKey("RoleId") 178 | .OnDelete(DeleteBehavior.Cascade); 179 | }); 180 | 181 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 182 | { 183 | b.HasOne("Onion.Web.Models.ApplicationUser") 184 | .WithMany("Claims") 185 | .HasForeignKey("UserId") 186 | .OnDelete(DeleteBehavior.Cascade); 187 | }); 188 | 189 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 190 | { 191 | b.HasOne("Onion.Web.Models.ApplicationUser") 192 | .WithMany("Logins") 193 | .HasForeignKey("UserId") 194 | .OnDelete(DeleteBehavior.Cascade); 195 | }); 196 | 197 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 198 | { 199 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 200 | .WithMany("Users") 201 | .HasForeignKey("RoleId") 202 | .OnDelete(DeleteBehavior.Cascade); 203 | 204 | b.HasOne("Onion.Web.Models.ApplicationUser") 205 | .WithMany("Roles") 206 | .HasForeignKey("UserId") 207 | .OnDelete(DeleteBehavior.Cascade); 208 | }); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Onion.Repo/Identity/Migrations/00000000000000_CreateIdentitySchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace Onion.Repo.Identity.Migrations 5 | { 6 | public partial class CreateIdentitySchema : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "AspNetRoles", 12 | columns: table => new 13 | { 14 | Id = table.Column(nullable: false), 15 | ConcurrencyStamp = table.Column(nullable: true), 16 | Name = table.Column(maxLength: 256, nullable: true), 17 | NormalizedName = table.Column(maxLength: 256, nullable: true) 18 | }, 19 | constraints: table => 20 | { 21 | table.PrimaryKey("PK_AspNetRoles", x => x.Id); 22 | }); 23 | 24 | migrationBuilder.CreateTable( 25 | name: "AspNetUserTokens", 26 | columns: table => new 27 | { 28 | UserId = table.Column(nullable: false), 29 | LoginProvider = table.Column(nullable: false), 30 | Name = table.Column(nullable: false), 31 | Value = table.Column(nullable: true) 32 | }, 33 | constraints: table => 34 | { 35 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 36 | }); 37 | 38 | migrationBuilder.CreateTable( 39 | name: "AspNetUsers", 40 | columns: table => new 41 | { 42 | Id = table.Column(nullable: false), 43 | AccessFailedCount = table.Column(nullable: false), 44 | ConcurrencyStamp = table.Column(nullable: true), 45 | Email = table.Column(maxLength: 256, nullable: true), 46 | EmailConfirmed = table.Column(nullable: false), 47 | LockoutEnabled = table.Column(nullable: false), 48 | LockoutEnd = table.Column(nullable: true), 49 | NormalizedEmail = table.Column(maxLength: 256, nullable: true), 50 | NormalizedUserName = table.Column(maxLength: 256, nullable: true), 51 | PasswordHash = table.Column(nullable: true), 52 | PhoneNumber = table.Column(nullable: true), 53 | PhoneNumberConfirmed = table.Column(nullable: false), 54 | SecurityStamp = table.Column(nullable: true), 55 | TwoFactorEnabled = table.Column(nullable: false), 56 | UserName = table.Column(maxLength: 256, nullable: true) 57 | }, 58 | constraints: table => 59 | { 60 | table.PrimaryKey("PK_AspNetUsers", x => x.Id); 61 | }); 62 | 63 | migrationBuilder.CreateTable( 64 | name: "AspNetRoleClaims", 65 | columns: table => new 66 | { 67 | Id = table.Column(nullable: false) 68 | .Annotation("Autoincrement", true), 69 | ClaimType = table.Column(nullable: true), 70 | ClaimValue = table.Column(nullable: true), 71 | RoleId = table.Column(nullable: false) 72 | }, 73 | constraints: table => 74 | { 75 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 76 | table.ForeignKey( 77 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", 78 | column: x => x.RoleId, 79 | principalTable: "AspNetRoles", 80 | principalColumn: "Id", 81 | onDelete: ReferentialAction.Cascade); 82 | }); 83 | 84 | migrationBuilder.CreateTable( 85 | name: "AspNetUserClaims", 86 | columns: table => new 87 | { 88 | Id = table.Column(nullable: false) 89 | .Annotation("Autoincrement", true), 90 | ClaimType = table.Column(nullable: true), 91 | ClaimValue = table.Column(nullable: true), 92 | UserId = table.Column(nullable: false) 93 | }, 94 | constraints: table => 95 | { 96 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 97 | table.ForeignKey( 98 | name: "FK_AspNetUserClaims_AspNetUsers_UserId", 99 | column: x => x.UserId, 100 | principalTable: "AspNetUsers", 101 | principalColumn: "Id", 102 | onDelete: ReferentialAction.Cascade); 103 | }); 104 | 105 | migrationBuilder.CreateTable( 106 | name: "AspNetUserLogins", 107 | columns: table => new 108 | { 109 | LoginProvider = table.Column(nullable: false), 110 | ProviderKey = table.Column(nullable: false), 111 | ProviderDisplayName = table.Column(nullable: true), 112 | UserId = table.Column(nullable: false) 113 | }, 114 | constraints: table => 115 | { 116 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 117 | table.ForeignKey( 118 | name: "FK_AspNetUserLogins_AspNetUsers_UserId", 119 | column: x => x.UserId, 120 | principalTable: "AspNetUsers", 121 | principalColumn: "Id", 122 | onDelete: ReferentialAction.Cascade); 123 | }); 124 | 125 | migrationBuilder.CreateTable( 126 | name: "AspNetUserRoles", 127 | columns: table => new 128 | { 129 | UserId = table.Column(nullable: false), 130 | RoleId = table.Column(nullable: false) 131 | }, 132 | constraints: table => 133 | { 134 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 135 | table.ForeignKey( 136 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId", 137 | column: x => x.RoleId, 138 | principalTable: "AspNetRoles", 139 | principalColumn: "Id", 140 | onDelete: ReferentialAction.Cascade); 141 | table.ForeignKey( 142 | name: "FK_AspNetUserRoles_AspNetUsers_UserId", 143 | column: x => x.UserId, 144 | principalTable: "AspNetUsers", 145 | principalColumn: "Id", 146 | onDelete: ReferentialAction.Cascade); 147 | }); 148 | 149 | migrationBuilder.CreateIndex( 150 | name: "RoleNameIndex", 151 | table: "AspNetRoles", 152 | column: "NormalizedName"); 153 | 154 | migrationBuilder.CreateIndex( 155 | name: "IX_AspNetRoleClaims_RoleId", 156 | table: "AspNetRoleClaims", 157 | column: "RoleId"); 158 | 159 | migrationBuilder.CreateIndex( 160 | name: "IX_AspNetUserClaims_UserId", 161 | table: "AspNetUserClaims", 162 | column: "UserId"); 163 | 164 | migrationBuilder.CreateIndex( 165 | name: "IX_AspNetUserLogins_UserId", 166 | table: "AspNetUserLogins", 167 | column: "UserId"); 168 | 169 | migrationBuilder.CreateIndex( 170 | name: "IX_AspNetUserRoles_RoleId", 171 | table: "AspNetUserRoles", 172 | column: "RoleId"); 173 | 174 | migrationBuilder.CreateIndex( 175 | name: "IX_AspNetUserRoles_UserId", 176 | table: "AspNetUserRoles", 177 | column: "UserId"); 178 | 179 | migrationBuilder.CreateIndex( 180 | name: "EmailIndex", 181 | table: "AspNetUsers", 182 | column: "NormalizedEmail"); 183 | 184 | migrationBuilder.CreateIndex( 185 | name: "UserNameIndex", 186 | table: "AspNetUsers", 187 | column: "NormalizedUserName", 188 | unique: true); 189 | } 190 | 191 | protected override void Down(MigrationBuilder migrationBuilder) 192 | { 193 | migrationBuilder.DropTable( 194 | name: "AspNetRoleClaims"); 195 | 196 | migrationBuilder.DropTable( 197 | name: "AspNetUserClaims"); 198 | 199 | migrationBuilder.DropTable( 200 | name: "AspNetUserLogins"); 201 | 202 | migrationBuilder.DropTable( 203 | name: "AspNetUserRoles"); 204 | 205 | migrationBuilder.DropTable( 206 | name: "AspNetUserTokens"); 207 | 208 | migrationBuilder.DropTable( 209 | name: "AspNetRoles"); 210 | 211 | migrationBuilder.DropTable( 212 | name: "AspNetUsers"); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /Onion.Repo/Identity/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | 5 | namespace Onion.Repo.Identity.Migrations 6 | { 7 | [DbContext(typeof(AppIdentityDbContext))] 8 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 9 | { 10 | protected override void BuildModel(ModelBuilder modelBuilder) 11 | { 12 | modelBuilder 13 | .HasAnnotation("ProductVersion", "1.0.2"); 14 | 15 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole", b => 16 | { 17 | b.Property("Id"); 18 | 19 | b.Property("ConcurrencyStamp") 20 | .IsConcurrencyToken(); 21 | 22 | b.Property("Name") 23 | .HasMaxLength(256); 24 | 25 | b.Property("NormalizedName") 26 | .HasMaxLength(256); 27 | 28 | b.HasKey("Id"); 29 | 30 | b.HasIndex("NormalizedName") 31 | .HasName("RoleNameIndex"); 32 | 33 | b.ToTable("AspNetRoles"); 34 | }); 35 | 36 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 37 | { 38 | b.Property("Id") 39 | .ValueGeneratedOnAdd(); 40 | 41 | b.Property("ClaimType"); 42 | 43 | b.Property("ClaimValue"); 44 | 45 | b.Property("RoleId") 46 | .IsRequired(); 47 | 48 | b.HasKey("Id"); 49 | 50 | b.HasIndex("RoleId"); 51 | 52 | b.ToTable("AspNetRoleClaims"); 53 | }); 54 | 55 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 56 | { 57 | b.Property("Id") 58 | .ValueGeneratedOnAdd(); 59 | 60 | b.Property("ClaimType"); 61 | 62 | b.Property("ClaimValue"); 63 | 64 | b.Property("UserId") 65 | .IsRequired(); 66 | 67 | b.HasKey("Id"); 68 | 69 | b.HasIndex("UserId"); 70 | 71 | b.ToTable("AspNetUserClaims"); 72 | }); 73 | 74 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 75 | { 76 | b.Property("LoginProvider"); 77 | 78 | b.Property("ProviderKey"); 79 | 80 | b.Property("ProviderDisplayName"); 81 | 82 | b.Property("UserId") 83 | .IsRequired(); 84 | 85 | b.HasKey("LoginProvider", "ProviderKey"); 86 | 87 | b.HasIndex("UserId"); 88 | 89 | b.ToTable("AspNetUserLogins"); 90 | }); 91 | 92 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 93 | { 94 | b.Property("UserId"); 95 | 96 | b.Property("RoleId"); 97 | 98 | b.HasKey("UserId", "RoleId"); 99 | 100 | b.HasIndex("RoleId"); 101 | 102 | b.HasIndex("UserId"); 103 | 104 | b.ToTable("AspNetUserRoles"); 105 | }); 106 | 107 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b => 108 | { 109 | b.Property("UserId"); 110 | 111 | b.Property("LoginProvider"); 112 | 113 | b.Property("Name"); 114 | 115 | b.Property("Value"); 116 | 117 | b.HasKey("UserId", "LoginProvider", "Name"); 118 | 119 | b.ToTable("AspNetUserTokens"); 120 | }); 121 | 122 | modelBuilder.Entity("Onion.Web.Models.ApplicationUser", b => 123 | { 124 | b.Property("Id"); 125 | 126 | b.Property("AccessFailedCount"); 127 | 128 | b.Property("ConcurrencyStamp") 129 | .IsConcurrencyToken(); 130 | 131 | b.Property("Email") 132 | .HasMaxLength(256); 133 | 134 | b.Property("EmailConfirmed"); 135 | 136 | b.Property("LockoutEnabled"); 137 | 138 | b.Property("LockoutEnd"); 139 | 140 | b.Property("NormalizedEmail") 141 | .HasMaxLength(256); 142 | 143 | b.Property("NormalizedUserName") 144 | .HasMaxLength(256); 145 | 146 | b.Property("PasswordHash"); 147 | 148 | b.Property("PhoneNumber"); 149 | 150 | b.Property("PhoneNumberConfirmed"); 151 | 152 | b.Property("SecurityStamp"); 153 | 154 | b.Property("TwoFactorEnabled"); 155 | 156 | b.Property("UserName") 157 | .HasMaxLength(256); 158 | 159 | b.HasKey("Id"); 160 | 161 | b.HasIndex("NormalizedEmail") 162 | .HasName("EmailIndex"); 163 | 164 | b.HasIndex("NormalizedUserName") 165 | .IsUnique() 166 | .HasName("UserNameIndex"); 167 | 168 | b.ToTable("AspNetUsers"); 169 | }); 170 | 171 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 172 | { 173 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 174 | .WithMany("Claims") 175 | .HasForeignKey("RoleId") 176 | .OnDelete(DeleteBehavior.Cascade); 177 | }); 178 | 179 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 180 | { 181 | b.HasOne("Onion.Web.Models.ApplicationUser") 182 | .WithMany("Claims") 183 | .HasForeignKey("UserId") 184 | .OnDelete(DeleteBehavior.Cascade); 185 | }); 186 | 187 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 188 | { 189 | b.HasOne("Onion.Web.Models.ApplicationUser") 190 | .WithMany("Logins") 191 | .HasForeignKey("UserId") 192 | .OnDelete(DeleteBehavior.Cascade); 193 | }); 194 | 195 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 196 | { 197 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 198 | .WithMany("Users") 199 | .HasForeignKey("RoleId") 200 | .OnDelete(DeleteBehavior.Cascade); 201 | 202 | b.HasOne("Onion.Web.Models.ApplicationUser") 203 | .WithMany("Roles") 204 | .HasForeignKey("UserId") 205 | .OnDelete(DeleteBehavior.Cascade); 206 | }); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Onion.Repo/Onion.Repo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Onion.Service/BookSeriesService.cs: -------------------------------------------------------------------------------- 1 | using Onion.Data.Entities; 2 | using Onion.Repo.Data; 3 | using Onion.Service.Interfaces; 4 | 5 | namespace Onion.Service 6 | { 7 | public class BookSeriesService : IBookSeriesService 8 | { 9 | private readonly IRepository _bookSeriesRepository; 10 | 11 | public BookSeriesService(IRepository bookSeriesRepository) 12 | { 13 | _bookSeriesRepository = bookSeriesRepository; 14 | } 15 | 16 | public BookSeries GetBookSeries(int id) 17 | { 18 | return _bookSeriesRepository.Get(id); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Onion.Service/BookService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Onion.Data.Entities; 3 | using Onion.Repo.Data; 4 | using Onion.Service.Interfaces; 5 | 6 | namespace Onion.Service 7 | { 8 | public class BookService : IBookService 9 | { 10 | private readonly IRepository _bookRepository; 11 | private readonly IRepository _bookSeriesRepository; 12 | 13 | public BookService(IRepository bookRepository, 14 | IRepository bookSeriesRepository) 15 | { 16 | _bookRepository = bookRepository; 17 | _bookSeriesRepository = bookSeriesRepository; 18 | } 19 | 20 | public IEnumerable GetBooks() 21 | { 22 | return _bookRepository.GetAll(); 23 | } 24 | 25 | public Book GetBook(int id) 26 | { 27 | return _bookRepository.Get(id); 28 | } 29 | 30 | public void InsertBook(Book book) 31 | { 32 | _bookRepository.Insert(book); 33 | } 34 | 35 | public void UpdateBook(Book book) 36 | { 37 | _bookRepository.Update(book); 38 | } 39 | 40 | public void DeleteBook(int id) 41 | { 42 | var bookSeries = _bookSeriesRepository.Get(id); 43 | _bookSeriesRepository.Delete(bookSeries); 44 | 45 | var user = _bookRepository.Get(id); 46 | _bookRepository.Delete(user); 47 | _bookRepository.SaveChanges(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Onion.Service/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Onion.Service.Interfaces; 3 | 4 | namespace Onion.Service 5 | { 6 | // This class is used by the application to send email for account confirmation and password reset. 7 | // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 8 | public class EmailSender : IEmailSender 9 | { 10 | public Task SendEmailAsync(string email, string subject, string message) 11 | { 12 | return Task.CompletedTask; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Onion.Service/Interfaces/IBookSeriesService.cs: -------------------------------------------------------------------------------- 1 | using Onion.Data.Entities; 2 | 3 | namespace Onion.Service.Interfaces 4 | { 5 | public interface IBookSeriesService 6 | { 7 | BookSeries GetBookSeries(int id); 8 | } 9 | } -------------------------------------------------------------------------------- /Onion.Service/Interfaces/IBookService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Onion.Data.Entities; 3 | 4 | namespace Onion.Service.Interfaces 5 | { 6 | public interface IBookService 7 | { 8 | IEnumerable GetBooks(); 9 | Book GetBook(int id); 10 | void InsertBook(Book book); 11 | void UpdateBook(Book book); 12 | void DeleteBook(int id); 13 | } 14 | } -------------------------------------------------------------------------------- /Onion.Service/Interfaces/IEmailSender.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Onion.Service.Interfaces 4 | { 5 | public interface IEmailSender 6 | { 7 | Task SendEmailAsync(string email, string subject, string message); 8 | } 9 | } -------------------------------------------------------------------------------- /Onion.Service/Interfaces/ISeriesService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Onion.Data.Entities; 3 | 4 | namespace Onion.Service.Interfaces 5 | { 6 | public interface ISeriesService 7 | { 8 | IEnumerable GetSeries(); 9 | Series GetSeries(int id); 10 | void InsertSeries(Series series); 11 | void UpdateSeries(Series series); 12 | void DeleteSeries(int id); 13 | } 14 | } -------------------------------------------------------------------------------- /Onion.Service/Onion.Service.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Onion.Service/SeriesService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Onion.Data.Entities; 3 | using Onion.Repo.Data; 4 | using Onion.Service.Interfaces; 5 | 6 | namespace Onion.Service 7 | { 8 | public class SeriesService : ISeriesService 9 | { 10 | private readonly IRepository _seriesRepository; 11 | private readonly IRepository _bookSeriesRepository; 12 | 13 | public SeriesService(IRepository seriesRepository, 14 | IRepository bookSeriesRepository) 15 | { 16 | _seriesRepository = seriesRepository; 17 | _bookSeriesRepository = bookSeriesRepository; 18 | } 19 | 20 | public IEnumerable GetSeries() 21 | { 22 | return _seriesRepository.GetAll(); 23 | } 24 | 25 | public Series GetSeries(int id) 26 | { 27 | return _seriesRepository.Get(id); 28 | } 29 | 30 | public void InsertSeries(Series series) 31 | { 32 | _seriesRepository.Insert(series); 33 | } 34 | 35 | public void UpdateSeries(Series series) 36 | { 37 | _seriesRepository.Update(series); 38 | } 39 | 40 | public void DeleteSeries(int id) 41 | { 42 | var bookSeries = _bookSeriesRepository.Get(id); 43 | _bookSeriesRepository.Delete(bookSeries); 44 | 45 | var user = _seriesRepository.Get(id); 46 | _seriesRepository.Delete(user); 47 | _seriesRepository.SaveChanges(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Onion.Web/ConfigureContainerExtentions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.AspNetCore.Mvc.Razor; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Onion.Repo; 6 | using Onion.Repo.Data; 7 | using Onion.Repo.Identity; 8 | using Onion.Service; 9 | using Onion.Service.Interfaces; 10 | 11 | namespace Onion.Web 12 | { 13 | public static class ConfigureContainerExtensions 14 | { 15 | public static void AddDbContext(this IServiceCollection serviceCollection, 16 | string dataConnectionString = null, string authConnectionString = null) 17 | { 18 | serviceCollection.AddDbContext(options => 19 | options.UseSqlite(dataConnectionString ?? GetDataConnectionStringFromConfig())); 20 | 21 | serviceCollection.AddDbContext(options => 22 | options.UseSqlite(authConnectionString ?? GetAuthConnectionFromConfig())); 23 | 24 | serviceCollection.AddIdentity() 25 | .AddEntityFrameworkStores(); 26 | } 27 | 28 | public static void AddRepository(this IServiceCollection serviceCollection) 29 | { 30 | serviceCollection.AddScoped(typeof(IRepository<>), typeof(DataRepository<>)); 31 | } 32 | 33 | public static void AddTransientServices(this IServiceCollection serviceCollection) 34 | { 35 | serviceCollection.AddTransient(); 36 | 37 | serviceCollection.AddTransient(); 38 | } 39 | 40 | /// 41 | /// Adds rules to the for dealing with Feature Folders 42 | /// 43 | /// 44 | /// The created in 45 | /// 46 | public static void AddFeatureFolders(this IServiceCollection serviceCollection) 47 | { 48 | serviceCollection.Configure(options => 49 | { 50 | options.ViewLocationExpanders.Add(new FeatureLocationExpander()); 51 | }); 52 | } 53 | 54 | private static string GetDataConnectionStringFromConfig() 55 | { 56 | return new DatabaseConfiguration().GetDataConnectionString(); 57 | } 58 | 59 | private static string GetAuthConnectionFromConfig() 60 | { 61 | return new DatabaseConfiguration().GetAuthConnectionString(); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Onion.Web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:2.0-stretch AS base 2 | WORKDIR /app 3 | EXPOSE 80 4 | 5 | FROM microsoft/aspnetcore-build:2.0-stretch AS build 6 | WORKDIR /src 7 | COPY ["Onion.Web/Onion.Web.csproj", "Onion.Web/"] 8 | COPY ["Onion.Data/Onion.Data.csproj", "Onion.Data/"] 9 | COPY ["Onion.Service/Onion.Service.csproj", "Onion.Service/"] 10 | COPY ["Onion.Repo/Onion.Repo.csproj", "Onion.Repo/"] 11 | RUN dotnet restore "Onion.Web/Onion.Web.csproj" 12 | COPY . . 13 | WORKDIR "/src/Onion.Web" 14 | RUN dotnet build "Onion.Web.csproj" -c Release -o /app/build 15 | 16 | FROM build AS publish 17 | RUN dotnet publish "Onion.Web.csproj" -c Release -o /app/publish 18 | 19 | FROM base AS final 20 | WORKDIR /app 21 | COPY --from=publish /app/publish . 22 | ENTRYPOINT ["dotnet", "Onion.Web.dll"] -------------------------------------------------------------------------------- /Onion.Web/Extensions/EmailSenderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Encodings.Web; 2 | using System.Threading.Tasks; 3 | using Onion.Service.Interfaces; 4 | 5 | namespace Onion.Web.Extensions 6 | { 7 | public static class EmailSenderExtensions 8 | { 9 | public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link) 10 | { 11 | return emailSender.SendEmailAsync(email, "Confirm your email", 12 | $"Please confirm your account by clicking this link: link"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Onion.Web/Extensions/UrlHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Onion.Web.Features.Account; 3 | 4 | namespace Onion.Web.Extensions 5 | { 6 | public static class UrlHelperExtensions 7 | { 8 | public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 9 | { 10 | return urlHelper.Action( 11 | action: nameof(AccountController.ConfirmEmail), 12 | controller: "Account", 13 | values: new { userId, code }, 14 | protocol: scheme); 15 | } 16 | 17 | public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 18 | { 19 | return urlHelper.Action( 20 | action: nameof(AccountController.ResetPassword), 21 | controller: "Account", 22 | values: new { userId, code }, 23 | protocol: scheme); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Onion.Web/FeatureLocationExpander.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using JetBrains.Annotations; 3 | using Microsoft.AspNetCore.Mvc.Razor; 4 | 5 | [assembly: AspMvcViewLocationFormat("~/Api/{1}{0}.cshtml")] 6 | [assembly: AspMvcViewLocationFormat("~/Features/{1}/{0}.cshtml")] 7 | [assembly: AspMvcViewLocationFormat("~/Features/Shared/{0}.cshtml")] 8 | namespace Onion.Web 9 | { 10 | /// 11 | /// Used to apply Features directorys (rather than separate Controller, Model 12 | /// and View directories). 13 | /// 14 | /// 15 | /// See: https://scottsauber.com/2016/04/25/feature-folder-structure-in-asp-net-core/ 16 | /// 17 | public class FeatureLocationExpander : IViewLocationExpander 18 | { 19 | public void PopulateValues(ViewLocationExpanderContext context) 20 | { 21 | // Don't need anything here, but required by the interface 22 | } 23 | 24 | public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, 25 | IEnumerable viewLocations) 26 | { 27 | // The old locations are /Views/{1}/{0}.cshtml and /Views/Shared/{0}.cshtml 28 | // where {1} is the controller and {0} is the name of the View 29 | 30 | // Replace /Views with /Features 31 | return new string[] 32 | { 33 | "/Api/{1}/{0}.cshtml", 34 | "/Features/{1}/{0}.cshtml", 35 | "/Features/Shared/{0}.cshtml" 36 | }; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Onion.Web/Features/Account/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Access denied"; 3 | } 4 | 5 |
6 |

ViewData["Title"]

7 |

You do not have access to this resource.

8 |
9 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/AccountViewModels/ExternalLoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Account.AccountViewModels 4 | { 5 | public class ExternalLoginViewModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | public string Email { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/AccountViewModels/ForgotPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Account.AccountViewModels 4 | { 5 | public class ForgotPasswordViewModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | public string Email { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/AccountViewModels/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Account.AccountViewModels 4 | { 5 | public class LoginViewModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | public string Email { get; set; } 10 | 11 | [Required] 12 | [DataType(DataType.Password)] 13 | public string Password { get; set; } 14 | 15 | [Display(Name = "Remember me?")] 16 | public bool RememberMe { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/AccountViewModels/LoginWith2faViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Account.AccountViewModels 4 | { 5 | public class LoginWith2faViewModel 6 | { 7 | [Required] 8 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 9 | [DataType(DataType.Text)] 10 | [Display(Name = "Authenticator code")] 11 | public string TwoFactorCode { get; set; } 12 | 13 | [Display(Name = "Remember this machine")] 14 | public bool RememberMachine { get; set; } 15 | 16 | public bool RememberMe { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/AccountViewModels/LoginWithRecoveryCodeViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Account.AccountViewModels 4 | { 5 | public class LoginWithRecoveryCodeViewModel 6 | { 7 | [Required] 8 | [DataType(DataType.Text)] 9 | [Display(Name = "Recovery Code")] 10 | public string RecoveryCode { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/AccountViewModels/RegisterViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Account.AccountViewModels 4 | { 5 | public class RegisterViewModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | [Display(Name = "Email")] 10 | public string Email { get; set; } 11 | 12 | [Required] 13 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 14 | [DataType(DataType.Password)] 15 | [Display(Name = "Password")] 16 | public string Password { get; set; } 17 | 18 | [DataType(DataType.Password)] 19 | [Display(Name = "Confirm password")] 20 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 21 | public string ConfirmPassword { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/AccountViewModels/ResetPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Account.AccountViewModels 4 | { 5 | public class ResetPasswordViewModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | public string Email { get; set; } 10 | 11 | [Required] 12 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 13 | [DataType(DataType.Password)] 14 | public string Password { get; set; } 15 | 16 | [DataType(DataType.Password)] 17 | [Display(Name = "Confirm password")] 18 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 19 | public string ConfirmPassword { get; set; } 20 | 21 | public string Code { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Confirm email"; 3 | } 4 | 5 |

@ViewData["Title"]

6 |
7 |

8 | Thank you for confirming your email. 9 |

10 |
11 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/ExternalLogin.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Account.AccountViewModels 2 | @model Onion.Web.Features.Account.AccountViewModels.ExternalLoginViewModel 3 | @{ 4 | ViewData["Title"] = "Register"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Associate your @ViewData["LoginProvider"] account.

9 |
10 | 11 |

12 | You've successfully authenticated with @ViewData["LoginProvider"]. 13 | Please enter an email address for this site below and click the Register button to finish 14 | logging in. 15 |

16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 |
30 | 31 | @section Scripts { 32 | @await Html.PartialAsync("_ValidationScriptsPartial") 33 | } 34 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Account.AccountViewModels 2 | @model Onion.Web.Features.Account.AccountViewModels.ForgotPasswordViewModel 3 | @{ 4 | ViewData["Title"] = "Forgot your password?"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Enter your email.

9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | @section Scripts { 25 | @await Html.PartialAsync("_ValidationScriptsPartial") 26 | } 27 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/ForgotPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Forgot password confirmation"; 3 | } 4 | 5 |

@ViewData["Title"]

6 |

7 | Please check your email to reset your password. 8 |

9 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Locked out"; 3 | } 4 | 5 |
6 |

@ViewData["Title"]

7 |

This account has been locked out, please try again later.

8 |
9 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Linq 2 | @using Onion.Repo.Identity 3 | @using Onion.Web.Features.Account.AccountViewModels 4 | @model Onion.Web.Features.Account.AccountViewModels.LoginViewModel 5 | @inject SignInManager SignInManager 6 | 7 | @{ 8 | ViewData["Title"] = "Log in"; 9 | } 10 | 11 |

@ViewData["Title"]

12 |
13 |
14 |
15 |
16 |

Use a local account to log in.

17 |
18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 | 27 | 28 |
29 |
30 |
31 | 35 |
36 |
37 |
38 | 39 |
40 |
41 |

42 | Forgot your password? 43 |

44 |

45 | Register as a new user? 46 |

47 |
48 |
49 |
50 |
51 |
52 |
53 |

Use another service to log in.

54 |
55 | @{ 56 | var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList(); 57 | if (loginProviders.Count == 0) 58 | { 59 |
60 |

61 | There are no external authentication services configured. See this article 62 | for details on setting up this ASP.NET application to support logging in via external services. 63 |

64 |
65 | } 66 | else 67 | { 68 |
69 |
70 |

71 | @foreach (var provider in loginProviders) 72 | { 73 | 74 | } 75 |

76 |
77 |
78 | } 79 | } 80 |
81 |
82 |
83 | 84 | @section Scripts { 85 | @await Html.PartialAsync("_ValidationScriptsPartial") 86 | } 87 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/LoginWith2fa.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Account.AccountViewModels 2 | @model Onion.Web.Features.Account.AccountViewModels.LoginWith2faViewModel 3 | @{ 4 | ViewData["Title"] = "Two-factor authentication"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

Your login is protected with an authenticator app. Enter your authenticator code below.

10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 |
22 | 26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |

35 | Don't have access to your authenticator device? You can 36 | log in with a recovery code. 37 |

38 | 39 | @section Scripts { 40 | @await Html.PartialAsync("_ValidationScriptsPartial") 41 | } -------------------------------------------------------------------------------- /Onion.Web/Features/Account/LoginWithRecoveryCode.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Account.AccountViewModels 2 | @model Onion.Web.Features.Account.AccountViewModels.LoginWithRecoveryCodeViewModel 3 | @{ 4 | ViewData["Title"] = "Recovery code verification"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

10 | You have requested to login with a recovery code. This login will not be remembered until you provide 11 | an authenticator app code at login or disable 2FA and login again. 12 |

13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 | @section Scripts { 28 | @await Html.PartialAsync("_ValidationScriptsPartial") 29 | } -------------------------------------------------------------------------------- /Onion.Web/Features/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @model Onion.Web.Features.Account.AccountViewModels.RegisterViewModel 2 | @{ 3 | ViewData["Title"] = "Register"; 4 | } 5 | 6 |

@ViewData["Title"]

7 | 8 |
9 |
10 |
11 |

Create a new account.

12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 | 27 | 28 |
29 | 30 |
31 |
32 |
33 | 34 | @section Scripts { 35 | @await Html.PartialAsync("_ValidationScriptsPartial") 36 | } 37 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Account.AccountViewModels 2 | @model Onion.Web.Features.Account.AccountViewModels.ResetPasswordViewModel 3 | @{ 4 | ViewData["Title"] = "Reset password"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Reset your password.

9 |
10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 |
34 | 35 | @section Scripts { 36 | @await Html.PartialAsync("_ValidationScriptsPartial") 37 | } 38 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/ResetPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Reset password confirmation"; 3 | } 4 | 5 |

@ViewData["Title"]

6 |

7 | Your password has been reset. Please click here to log in. 8 |

9 | -------------------------------------------------------------------------------- /Onion.Web/Features/Account/SignedOut.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Signed out"; 3 | } 4 | 5 |

@ViewData["Title"]

6 |

7 | You have successfully signed out. 8 |

9 | -------------------------------------------------------------------------------- /Onion.Web/Features/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Onion.Web.Features 2 | { 3 | public class ErrorViewModel 4 | { 5 | public string RequestId { get; set; } 6 | 7 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 8 | } 9 | } -------------------------------------------------------------------------------- /Onion.Web/Features/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /Onion.Web/Features/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /Onion.Web/Features/Home/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Onion.Service.Interfaces; 4 | 5 | namespace Onion.Web.Features.Home 6 | { 7 | public class HomeController : Controller 8 | { 9 | private readonly IBookService _bookService; 10 | 11 | public HomeController(IBookService bookService) 12 | { 13 | _bookService = bookService; 14 | } 15 | 16 | public IActionResult Index() 17 | { 18 | return View(); 19 | } 20 | 21 | public IActionResult About() 22 | { 23 | ViewData["Message"] = "Your application description page."; 24 | 25 | return View(); 26 | } 27 | 28 | public IActionResult Contact() 29 | { 30 | ViewData["Message"] = "Your contact page."; 31 | 32 | return View(); 33 | } 34 | 35 | public IActionResult Error() 36 | { 37 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Onion.Web/Features/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | 67 | 68 |
69 |
70 |

Application uses

71 |
    72 |
  • Sample pages using ASP.NET Core MVC
  • 73 |
  • Bower for managing client-side libraries
  • 74 |
  • Theming using Bootstrap
  • 75 |
76 |
77 | 88 | 100 |
101 |

Run & Deploy

102 | 107 |
108 |
109 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Manage 2 | @using Onion.Web.Features.Manage.ViewModels 3 | @model ChangePasswordViewModel 4 | @{ 5 | ViewData["Title"] = "Change password"; 6 | ViewData.AddActivePage(ManageNavPages.ChangePassword); 7 | } 8 | 9 |

@ViewData["Title"]

10 | @Html.Partial("_StatusMessage", Model.StatusMessage) 11 |
12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 |
34 | 35 | @section Scripts { 36 | @await Html.PartialAsync("_ValidationScriptsPartial") 37 | } 38 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/Disable2fa.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Manage 2 | @{ 3 | ViewData["Title"] = "Disable two-factor authentication (2FA)"; 4 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 5 | } 6 | 7 |

@ViewData["Title"]

8 | 9 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/EnableAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Manage 2 | @using Onion.Web.Features.Manage.ViewModels 3 | @model EnableAuthenticatorViewModel 4 | @{ 5 | ViewData["Title"] = "Enable authenticator"; 6 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 7 | } 8 | 9 |

@ViewData["Title"]

10 |
11 |

To use an authenticator app go through the following steps:

12 |
    13 |
  1. 14 |

    15 | Download a two-factor authenticator app like Microsoft Authenticator for 16 | Windows Phone, 17 | Android and 18 | iOS or 19 | Google Authenticator for 20 | Android and 21 | iOS. 22 |

    23 |
  2. 24 |
  3. 25 |

    Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.

    26 |
    To enable QR code generation please read our documentation.
    27 |
    28 |
    29 |
  4. 30 |
  5. 31 |

    32 | Once you have scanned the QR code or input the key above, your two factor authentication app will provide you 33 | with a unique code. Enter the code in the confirmation box below. 34 |

    35 |
    36 |
    37 |
    38 |
    39 | 40 | 41 | 42 |
    43 | 44 |
    45 |
    46 |
    47 |
    48 |
  6. 49 |
50 |
51 | 52 | @section Scripts { 53 | @await Html.PartialAsync("_ValidationScriptsPartial") 54 | } 55 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ExternalLogins.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Manage 2 | @using Onion.Web.Features.Manage.ViewModels 3 | @model ExternalLoginsViewModel 4 | @{ 5 | ViewData["Title"] = "Manage your external logins"; 6 | ViewData.AddActivePage(ManageNavPages.ExternalLogins); 7 | } 8 | 9 | @Html.Partial("_StatusMessage", Model.StatusMessage) 10 | @if (Model.CurrentLogins?.Count > 0) 11 | { 12 |

Registered Logins

13 | 14 | 15 | @foreach (var login in Model.CurrentLogins) 16 | { 17 | 18 | 19 | 35 | 36 | } 37 | 38 |
@login.LoginProvider 20 | @if (Model.ShowRemoveButton) 21 | { 22 |
23 |
24 | 25 | 26 | 27 |
28 |
29 | } 30 | else 31 | { 32 | @:   33 | } 34 |
39 | } 40 | @if (Model.OtherLogins?.Count > 0) 41 | { 42 |

Add another service to log in.

43 |
44 |
45 |
46 |

47 | @foreach (var provider in Model.OtherLogins) 48 | { 49 | 50 | } 51 |

52 |
53 |
54 | } 55 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/GenerateRecoveryCodes.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Manage 2 | @using Onion.Web.Features.Manage.ViewModels 3 | @model GenerateRecoveryCodesViewModel 4 | @{ 5 | ViewData["Title"] = "Recovery codes"; 6 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 7 | } 8 | 9 |

@ViewData["Title"]

10 | 19 |
20 |
21 | @for (var row = 0; row < Model.RecoveryCodes.Count(); row += 2) 22 | { 23 | @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
24 | } 25 |
26 |
-------------------------------------------------------------------------------- /Onion.Web/Features/Manage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Manage 2 | @using Onion.Web.Features.Manage.ViewModels 3 | @model IndexViewModel 4 | @{ 5 | ViewData["Title"] = "Profile"; 6 | ViewData.AddActivePage(ManageNavPages.Index); 7 | } 8 | 9 |

@ViewData["Title"]

10 | @Html.Partial("_StatusMessage", Model.StatusMessage) 11 |
12 |
13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | @if (Model.IsEmailConfirmed) 22 | { 23 |
24 | 25 | 26 |
27 | } 28 | else 29 | { 30 | 31 | 32 | } 33 | 34 |
35 |
36 | 37 | 38 | 39 |
40 | 41 |
42 |
43 |
44 | 45 | @section Scripts { 46 | @await Html.PartialAsync("_ValidationScriptsPartial") 47 | } 48 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ManageNavPages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc.Rendering; 3 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 4 | 5 | namespace Onion.Web.Features.Manage 6 | { 7 | public static class ManageNavPages 8 | { 9 | public static string ActivePageKey => "ActivePage"; 10 | 11 | public static string Index => "Index"; 12 | 13 | public static string ChangePassword => "ChangePassword"; 14 | 15 | public static string ExternalLogins => "ExternalLogins"; 16 | 17 | public static string TwoFactorAuthentication => "TwoFactorAuthentication"; 18 | 19 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); 20 | 21 | public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); 22 | 23 | public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); 24 | 25 | public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); 26 | 27 | public static string PageNavClass(ViewContext viewContext, string page) 28 | { 29 | var activePage = viewContext.ViewData["ActivePage"] as string; 30 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; 31 | } 32 | 33 | public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ResetAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Manage 2 | @{ 3 | ViewData["Title"] = "Reset authenticator key"; 4 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 5 | } 6 | 7 |

@ViewData["Title"]

8 | 18 |
19 |
20 | 21 |
22 |
-------------------------------------------------------------------------------- /Onion.Web/Features/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Manage 2 | @using Onion.Web.Features.Manage.ViewModels 3 | @model SetPasswordViewModel 4 | @{ 5 | ViewData["Title"] = "Set password"; 6 | ViewData.AddActivePage(ManageNavPages.ChangePassword); 7 | } 8 | 9 |

Set your password

10 | @Html.Partial("_StatusMessage", Model.StatusMessage) 11 |

12 | You do not have a local username/password for this site. Add a local 13 | account so you can log in without an external login. 14 |

15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 | 27 | 28 |
29 | 30 |
31 |
32 |
33 | 34 | @section Scripts { 35 | @await Html.PartialAsync("_ValidationScriptsPartial") 36 | } 37 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/TwoFactorAuthentication.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features.Manage 2 | @using Onion.Web.Features.Manage.ViewModels 3 | @model TwoFactorAuthenticationViewModel 4 | @{ 5 | ViewData["Title"] = "Two-factor authentication"; 6 | ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication); 7 | } 8 | 9 |

@ViewData["Title"]

10 | @if (Model.Is2faEnabled) 11 | { 12 | if (Model.RecoveryCodesLeft == 0) 13 | { 14 |
15 | You have no recovery codes left. 16 |

You must generate a new set of recovery codes before you can log in with a recovery code.

17 |
18 | } 19 | else if (Model.RecoveryCodesLeft == 1) 20 | { 21 |
22 | You have 1 recovery code left. 23 |

You can generate a new set of recovery codes.

24 |
25 | } 26 | else if (Model.RecoveryCodesLeft <= 3) 27 | { 28 |
29 | You have @Model.RecoveryCodesLeft recovery codes left. 30 |

You should generate a new set of recovery codes.

31 |
32 | } 33 | 34 | Disable 2FA 35 | Reset recovery codes 36 | } 37 | 38 |
Authenticator app
39 | @if (!Model.HasAuthenticator) 40 | { 41 | Add authenticator app 42 | } 43 | else 44 | { 45 | Configure authenticator app 46 | Reset authenticator key 47 | } 48 | 49 | @section Scripts { 50 | @await Html.PartialAsync("_ValidationScriptsPartial") 51 | } 52 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ViewModels/ChangePasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Manage.ViewModels 4 | { 5 | public class ChangePasswordViewModel 6 | { 7 | [Required] 8 | [DataType(DataType.Password)] 9 | [Display(Name = "Current password")] 10 | public string OldPassword { get; set; } 11 | 12 | [Required] 13 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 14 | [DataType(DataType.Password)] 15 | [Display(Name = "New password")] 16 | public string NewPassword { get; set; } 17 | 18 | [DataType(DataType.Password)] 19 | [Display(Name = "Confirm new password")] 20 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 21 | public string ConfirmPassword { get; set; } 22 | 23 | public string StatusMessage { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ViewModels/EnableAuthenticatorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Onion.Web.Features.Manage.ViewModels 5 | { 6 | public class EnableAuthenticatorViewModel 7 | { 8 | [Required] 9 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 10 | [DataType(DataType.Text)] 11 | [Display(Name = "Verification Code")] 12 | public string Code { get; set; } 13 | 14 | [ReadOnly(true)] 15 | public string SharedKey { get; set; } 16 | 17 | public string AuthenticatorUri { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ViewModels/ExternalLoginsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Authentication; 3 | using Microsoft.AspNetCore.Identity; 4 | 5 | namespace Onion.Web.Features.Manage.ViewModels 6 | { 7 | public class ExternalLoginsViewModel 8 | { 9 | public IList CurrentLogins { get; set; } 10 | 11 | public IList OtherLogins { get; set; } 12 | 13 | public bool ShowRemoveButton { get; set; } 14 | 15 | public string StatusMessage { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ViewModels/GenerateRecoveryCodesViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Onion.Web.Features.Manage.ViewModels 2 | { 3 | public class GenerateRecoveryCodesViewModel 4 | { 5 | public string[] RecoveryCodes { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ViewModels/IndexViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Manage.ViewModels 4 | { 5 | public class IndexViewModel 6 | { 7 | public string Username { get; set; } 8 | 9 | public bool IsEmailConfirmed { get; set; } 10 | 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | 15 | [Phone] 16 | [Display(Name = "Phone number")] 17 | public string PhoneNumber { get; set; } 18 | 19 | public string StatusMessage { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ViewModels/RemoveLoginViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Onion.Web.Features.Manage.ViewModels 2 | { 3 | public class RemoveLoginViewModel 4 | { 5 | public string LoginProvider { get; set; } 6 | public string ProviderKey { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ViewModels/SetPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Onion.Web.Features.Manage.ViewModels 4 | { 5 | public class SetPasswordViewModel 6 | { 7 | [Required] 8 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 9 | [DataType(DataType.Password)] 10 | [Display(Name = "New password")] 11 | public string NewPassword { get; set; } 12 | 13 | [DataType(DataType.Password)] 14 | [Display(Name = "Confirm new password")] 15 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 16 | public string ConfirmPassword { get; set; } 17 | 18 | public string StatusMessage { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/ViewModels/TwoFactorAuthenticationViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Onion.Web.Features.Manage.ViewModels 2 | { 3 | public class TwoFactorAuthenticationViewModel 4 | { 5 | public bool HasAuthenticator { get; set; } 6 | 7 | public int RecoveryCodesLeft { get; set; } 8 | 9 | public bool Is2faEnabled { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Views/Shared/_Layout.cshtml"; 3 | } 4 | 5 |

Manage your account

6 | 7 |
8 |

Change your account settings

9 |
10 |
11 |
12 | @await Html.PartialAsync("_ManageNav") 13 |
14 |
15 | @RenderBody() 16 |
17 |
18 |
19 | 20 | @section Scripts { 21 | @RenderSection("Scripts", required: false) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/_ManageNav.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Linq 2 | @using Microsoft.AspNetCore.Identity 3 | @using Onion.Repo.Identity 4 | @using Onion.Web.Features.Manage 5 | @inject SignInManager SignInManager 6 | @{ 7 | var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); 8 | } 9 | 10 | 19 | 20 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/_StatusMessage.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 | @if (!string.IsNullOrEmpty(Model)) 4 | { 5 | var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; 6 | 10 | } 11 | -------------------------------------------------------------------------------- /Onion.Web/Features/Manage/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Onion.Web/Features/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Web.Features 2 | @model Onion.Web.Features.ErrorViewModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 |

21 |

22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |

24 | -------------------------------------------------------------------------------- /Onion.Web/Features/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - Onion.Web 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 41 |
42 | @RenderBody() 43 |
44 |
45 |

© 2017 - Onion.Web

46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 61 | 67 | 68 | 69 | 70 | @RenderSection("Scripts", required: false) 71 | 72 | 73 | -------------------------------------------------------------------------------- /Onion.Web/Features/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Onion.Repo.Identity 2 | 3 | @inject SignInManager SignInManager 4 | @inject UserManager UserManager 5 | 6 | @if (SignInManager.IsSignedIn(User)) 7 | { 8 | 18 | } 19 | else 20 | { 21 | 25 | } 26 | -------------------------------------------------------------------------------- /Onion.Web/Features/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /Onion.Web/Features/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using Onion.Web 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /Onion.Web/Features/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Onion.Web/Onion.Web.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | aspnet-Onion.Web-727D117F-8A93-4C18-BEAC-B62DEA978353 5 | Linux 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Onion.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace Onion.Web 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Onion.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:58026/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Onion.Web": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:58027/" 25 | }, 26 | "Docker": { 27 | "commandName": "Docker", 28 | "launchBrowser": true, 29 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", 30 | "environmentVariables": {}, 31 | "httpPort": 58027 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Onion.Web/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | //#if(enable-gnu-pratchett) 6 | using ClacksMiddleware.Extensions; 7 | //#endif 8 | //#if(enable-secure-headers) 9 | using OwaspHeaders.Core.Enums; 10 | using OwaspHeaders.Core.Extensions; 11 | //#endif 12 | 13 | namespace Onion.Web 14 | { 15 | public class Startup 16 | { 17 | public Startup(IConfiguration configuration) 18 | { 19 | Configuration = configuration; 20 | } 21 | 22 | public IConfiguration Configuration { get; } 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | services.AddDbContext(); 28 | 29 | services.AddRepository(); 30 | 31 | services.AddTransientServices(); 32 | 33 | services.AddMvc(); 34 | 35 | services.AddFeatureFolders(); 36 | } 37 | 38 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 39 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 40 | { 41 | if (env.IsDevelopment()) 42 | { 43 | app.UseDeveloperExceptionPage(); 44 | app.UseDatabaseErrorPage(); 45 | } 46 | else 47 | { 48 | app.UseExceptionHandler("/Home/Error"); 49 | } 50 | 51 | //#if(enable-gnu-pratchett) 52 | app.GnuTerryPratchett(); 53 | //#endif 54 | //#if(enable-secure-headers) 55 | app.UseSecureHeadersMiddleware(SecureHeadersMiddlewareExtensions.BuildDefaultConfiguration()); 56 | //#endif 57 | 58 | app.UseStaticFiles(); 59 | 60 | app.UseAuthentication(); 61 | 62 | app.UseMvc(routes => 63 | { 64 | routes.MapRoute( 65 | name: "default", 66 | template: "{controller=Home}/{action=Index}/{id?}"); 67 | }); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Onion.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Onion.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "onionDataConnection": "DataSource=onionData.db", 4 | "onionAuthConnection": "DataSource=onionAuth.db" 5 | }, 6 | "Logging": { 7 | "IncludeScopes": false, 8 | "LogLevel": { 9 | "Default": "Warning" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Onion.Web/bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [ 4 | { 5 | "outputFileName": "wwwroot/css/site.min.css", 6 | // An array of relative input file paths. Globbing patterns supported 7 | "inputFiles": [ 8 | "wwwroot/css/site.css" 9 | ] 10 | }, 11 | { 12 | "outputFileName": "wwwroot/js/site.min.js", 13 | "inputFiles": [ 14 | "wwwroot/js/site.js" 15 | ], 16 | // Optionally specify minification options 17 | "minify": { 18 | "enabled": true, 19 | "renameLocals": true 20 | }, 21 | // Optionally generate .map file 22 | "sourceMap": false 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /Onion.Web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onion.web", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "GaProgMan", 6 | "scripts": { 7 | "build": "webpack --mode development", 8 | "build-production": "webpack --mode production" 9 | }, 10 | "license": "MIT", 11 | "private": true, 12 | "dependencies": { 13 | "bootstrap": "^3.4.1", 14 | "jquery": "^3.4.0", 15 | "jquery-validation": "^1.14.0", 16 | "jquery-validation-unobtrusive": "^3.2.6" 17 | }, 18 | "devDependencies": { 19 | "copy-webpack-plugin": "^5.0.4", 20 | "webpack": "^4.41.0", 21 | "webpack-cli": "^3.3.9" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Onion.Web/webpack.assets.js: -------------------------------------------------------------------------------- 1 | const CSS = [ 2 | 'bootstrap/dist/css' 3 | ]; 4 | const JS = [ 5 | 'bootstrap/dist/js', 6 | 'jquery/dist', 7 | 'jquery-validation/dist/additional-methods.js', 8 | 'jquery-validation/dist/jquery.validate.js', 9 | 'jquery-validation-unobtrusive/jquery.validate.unobtrusive.js', 10 | ]; 11 | const Fonts = [ 12 | 'bootstrap/dist/fonts' 13 | ] 14 | 15 | module.exports = [...JS, ...CSS, ...Fonts]; 16 | -------------------------------------------------------------------------------- /Onion.Web/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | 5 | const Assets = require('./webpack.assets'); 6 | 7 | module.exports = { 8 | entry: { 9 | app: './wwwroot/js/site.js', 10 | }, 11 | output: { 12 | path: __dirname + '/wwwroot/', 13 | filename: '[name].bundle.js' 14 | }, 15 | plugins: [ 16 | new CopyWebpackPlugin( 17 | Assets.map(asset => { 18 | return { 19 | from: path.resolve(__dirname, `./node_modules/${asset}`), 20 | to: path.resolve(__dirname, `./wwwroot/lib/${asset}`) 21 | }; 22 | }) 23 | ) 24 | ] 25 | }; 26 | -------------------------------------------------------------------------------- /Onion.Web/wwwroot/app.bundle.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // define getter function for harmony exports 37 | /******/ __webpack_require__.d = function(exports, name, getter) { 38 | /******/ if(!__webpack_require__.o(exports, name)) { 39 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 40 | /******/ } 41 | /******/ }; 42 | /******/ 43 | /******/ // define __esModule on exports 44 | /******/ __webpack_require__.r = function(exports) { 45 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 46 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 47 | /******/ } 48 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 49 | /******/ }; 50 | /******/ 51 | /******/ // create a fake namespace object 52 | /******/ // mode & 1: value is a module id, require it 53 | /******/ // mode & 2: merge all properties of value into the ns 54 | /******/ // mode & 4: return value when already ns object 55 | /******/ // mode & 8|1: behave like require 56 | /******/ __webpack_require__.t = function(value, mode) { 57 | /******/ if(mode & 1) value = __webpack_require__(value); 58 | /******/ if(mode & 8) return value; 59 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 60 | /******/ var ns = Object.create(null); 61 | /******/ __webpack_require__.r(ns); 62 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 63 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 64 | /******/ return ns; 65 | /******/ }; 66 | /******/ 67 | /******/ // getDefaultExport function for compatibility with non-harmony modules 68 | /******/ __webpack_require__.n = function(module) { 69 | /******/ var getter = module && module.__esModule ? 70 | /******/ function getDefault() { return module['default']; } : 71 | /******/ function getModuleExports() { return module; }; 72 | /******/ __webpack_require__.d(getter, 'a', getter); 73 | /******/ return getter; 74 | /******/ }; 75 | /******/ 76 | /******/ // Object.prototype.hasOwnProperty.call 77 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 78 | /******/ 79 | /******/ // __webpack_public_path__ 80 | /******/ __webpack_require__.p = ""; 81 | /******/ 82 | /******/ 83 | /******/ // Load entry module and return exports 84 | /******/ return __webpack_require__(__webpack_require__.s = "./wwwroot/js/site.js"); 85 | /******/ }) 86 | /************************************************************************/ 87 | /******/ ({ 88 | 89 | /***/ "./wwwroot/js/site.js": 90 | /*!****************************!*\ 91 | !*** ./wwwroot/js/site.js ***! 92 | \****************************/ 93 | /*! no static exports found */ 94 | /***/ (function(module, exports) { 95 | 96 | eval("// Write your JavaScript code.\r\n\n\n//# sourceURL=webpack:///./wwwroot/js/site.js?"); 97 | 98 | /***/ }) 99 | 100 | /******/ }); -------------------------------------------------------------------------------- /Onion.Web/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Carousel */ 14 | .carousel-caption p { 15 | font-size: 20px; 16 | line-height: 1.4; 17 | } 18 | 19 | /* Make .svg files in the carousel display properly in older browsers */ 20 | .carousel-inner .item img[src$=".svg"] { 21 | width: 100%; 22 | } 23 | 24 | /* QR code generator */ 25 | #qrCode { 26 | margin: 15px; 27 | } 28 | 29 | /* Hide/rearrange for smaller screens */ 30 | @media screen and (max-width: 767px) { 31 | /* Hide captions */ 32 | .carousel-caption { 33 | display: none; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Onion.Web/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /Onion.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaProgMan/OnionArch/9c7517c3bead83a75f552f10fdcf79bed117767e/Onion.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Onion.Web/wwwroot/images/banner1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Onion.Web/wwwroot/images/banner2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Onion.Web/wwwroot/images/banner3.svg: -------------------------------------------------------------------------------- 1 | banner3b -------------------------------------------------------------------------------- /Onion.Web/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your JavaScript code. 2 | -------------------------------------------------------------------------------- /Onion.Web/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaProgMan/OnionArch/9c7517c3bead83a75f552f10fdcf79bed117767e/Onion.Web/wwwroot/js/site.min.js -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | }, 34 | "version": "3.3.7", 35 | "_release": "3.3.7", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.7", 39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" 40 | }, 41 | "_source": "https://github.com/twbs/bootstrap.git", 42 | "_target": "v3.3.7", 43 | "_originalSource": "bootstrap", 44 | "_direct": true 45 | } -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Twitter, Inc. 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. 22 | -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaProgMan/OnionArch/9c7517c3bead83a75f552f10fdcf79bed117767e/Onion.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaProgMan/OnionArch/9c7517c3bead83a75f552f10fdcf79bed117767e/Onion.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaProgMan/OnionArch/9c7517c3bead83a75f552f10fdcf79bed117767e/Onion.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaProgMan/OnionArch/9c7517c3bead83a75f552f10fdcf79bed117767e/Onion.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/bootstrap/dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/jquery-validation-unobtrusive/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation-unobtrusive", 3 | "version": "3.2.6", 4 | "homepage": "https://github.com/aspnet/jquery-validation-unobtrusive", 5 | "description": "Add-on to jQuery Validation to enable unobtrusive validation options in data-* attributes.", 6 | "main": [ 7 | "jquery.validate.unobtrusive.js" 8 | ], 9 | "ignore": [ 10 | "**/.*", 11 | "*.json", 12 | "*.md", 13 | "*.txt", 14 | "gulpfile.js" 15 | ], 16 | "keywords": [ 17 | "jquery", 18 | "asp.net", 19 | "mvc", 20 | "validation", 21 | "unobtrusive" 22 | ], 23 | "authors": [ 24 | "Microsoft" 25 | ], 26 | "license": "http://www.microsoft.com/web/webpi/eula/net_library_eula_enu.htm", 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/aspnet/jquery-validation-unobtrusive.git" 30 | }, 31 | "dependencies": { 32 | "jquery-validation": ">=1.8", 33 | "jquery": ">=1.8" 34 | }, 35 | "_release": "3.2.6", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.2.6", 39 | "commit": "13386cd1b5947d8a5d23a12b531ce3960be1eba7" 40 | }, 41 | "_source": "git://github.com/aspnet/jquery-validation-unobtrusive.git", 42 | "_target": "3.2.6", 43 | "_originalSource": "jquery-validation-unobtrusive" 44 | } -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Unobtrusive validation support library for jQuery and jQuery Validate 3 | ** Copyright (C) Microsoft Corporation. All rights reserved. 4 | */ 5 | !function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("
  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function m(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=p.unobtrusive.options||{},m=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),m("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),m("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),m("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var u,p=a.validator,v="unobtrusiveValidation";p.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=m(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){p.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=m(this);a&&a.attachValidation()})}},u=p.unobtrusive.adapters,u.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},u.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},u.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},u.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},p.addMethod("__dummy__",function(a,e,n){return!0}),p.addMethod("regex",function(a,e,n){var t;return this.optional(e)?!0:(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),p.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),p.methods.extension?(u.addSingleVal("accept","mimtype"),u.addSingleVal("extension","extension")):u.addSingleVal("extension","extension","accept"),u.addSingleVal("regex","pattern"),u.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),u.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),u.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),u.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),u.add("required",function(a){("INPUT"!==a.element.tagName.toUpperCase()||"CHECKBOX"!==a.element.type.toUpperCase())&&e(a,"required",!0)}),u.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),u.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),a(function(){p.unobtrusive.parse(document)})}(jQuery); -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/jquery-validation/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation", 3 | "homepage": "http://jqueryvalidation.org/", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/jzaefferer/jquery-validation.git" 7 | }, 8 | "authors": [ 9 | "Jörn Zaefferer " 10 | ], 11 | "description": "Form validation made easy", 12 | "main": "dist/jquery.validate.js", 13 | "keywords": [ 14 | "forms", 15 | "validation", 16 | "validate" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "demo", 25 | "lib" 26 | ], 27 | "dependencies": { 28 | "jquery": ">= 1.7.2" 29 | }, 30 | "version": "1.14.0", 31 | "_release": "1.14.0", 32 | "_resolution": { 33 | "type": "version", 34 | "tag": "1.14.0", 35 | "commit": "c1343fb9823392aa9acbe1c3ffd337b8c92fed48" 36 | }, 37 | "_source": "git://github.com/jzaefferer/jquery-validation.git", 38 | "_target": ">=1.8", 39 | "_originalSource": "jquery-validation" 40 | } -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/jquery/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "main": "dist/jquery.js", 4 | "license": "MIT", 5 | "ignore": [ 6 | "package.json" 7 | ], 8 | "keywords": [ 9 | "jquery", 10 | "javascript", 11 | "browser", 12 | "library" 13 | ], 14 | "homepage": "https://github.com/jquery/jquery-dist", 15 | "version": "2.2.0", 16 | "_release": "2.2.0", 17 | "_resolution": { 18 | "type": "version", 19 | "tag": "2.2.0", 20 | "commit": "6fc01e29bdad0964f62ef56d01297039cdcadbe5" 21 | }, 22 | "_source": "git://github.com/jquery/jquery-dist.git", 23 | "_target": "2.2.0", 24 | "_originalSource": "jquery" 25 | } -------------------------------------------------------------------------------- /Onion.Web/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /OnionArch.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OnionArch.Mvc 5 | OnionArch.Mvc 6 | 0.0.5 7 | https://raw.githubusercontent.com/GaProgMan/OnionArch/master/onionArch-nuget-logo.png 8 | 9 | ASP.NET Core MVC using the onion architecture and feature folders 10 | 11 | 12 | Creates a new ASP NET Core MVC application using the onion architecture and feature folders 13 | 14 | false 15 | Jamie Taylor 16 | 17 | 18 | 19 | Copyright Jamie Taylor 2018 20 | https://raw.githubusercontent.com/GaProgMan/OnionArch/master/LICENSE 21 | https://github.com/GaProgMan/OnionArch 22 | Template, AspNet, Mvc, onion architecture, feature folders 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /OnionArch.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29326.143 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Onion.Data", "Onion.Data\Onion.Data.csproj", "{9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Onion.Repo", "Onion.Repo\Onion.Repo.csproj", "{C5217DF9-58A1-4927-A362-E872D08DD938}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Onion.Service", "Onion.Service\Onion.Service.csproj", "{C864A59A-7CC6-4A7A-B644-EEE589F84C03}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Onion.Web", "Onion.Web\Onion.Web.csproj", "{84FB7387-0A7D-4B73-B724-0C79FFB821FD}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Debug|x64 = Debug|x64 18 | Debug|x86 = Debug|x86 19 | Release|Any CPU = Release|Any CPU 20 | Release|x64 = Release|x64 21 | Release|x86 = Release|x86 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Debug|x64.ActiveCfg = Debug|Any CPU 27 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Debug|x64.Build.0 = Debug|Any CPU 28 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Debug|x86.Build.0 = Debug|Any CPU 30 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Release|x64.ActiveCfg = Release|Any CPU 33 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Release|x64.Build.0 = Release|Any CPU 34 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Release|x86.ActiveCfg = Release|Any CPU 35 | {9BB9E230-0DA8-4F98-ACE6-C1F0B94157BC}.Release|x86.Build.0 = Release|Any CPU 36 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Debug|x64.ActiveCfg = Debug|Any CPU 39 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Debug|x64.Build.0 = Debug|Any CPU 40 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Debug|x86.Build.0 = Debug|Any CPU 42 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Release|x64.ActiveCfg = Release|Any CPU 45 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Release|x64.Build.0 = Release|Any CPU 46 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Release|x86.ActiveCfg = Release|Any CPU 47 | {C5217DF9-58A1-4927-A362-E872D08DD938}.Release|x86.Build.0 = Release|Any CPU 48 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Debug|x64.ActiveCfg = Debug|Any CPU 51 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Debug|x64.Build.0 = Debug|Any CPU 52 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Debug|x86.ActiveCfg = Debug|Any CPU 53 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Debug|x86.Build.0 = Debug|Any CPU 54 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Release|x64.ActiveCfg = Release|Any CPU 57 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Release|x64.Build.0 = Release|Any CPU 58 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Release|x86.ActiveCfg = Release|Any CPU 59 | {C864A59A-7CC6-4A7A-B644-EEE589F84C03}.Release|x86.Build.0 = Release|Any CPU 60 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Debug|x64.ActiveCfg = Debug|Any CPU 63 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Debug|x64.Build.0 = Debug|Any CPU 64 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Debug|x86.ActiveCfg = Debug|Any CPU 65 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Debug|x86.Build.0 = Debug|Any CPU 66 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Release|x64.ActiveCfg = Release|Any CPU 69 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Release|x64.Build.0 = Release|Any CPU 70 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Release|x86.ActiveCfg = Release|Any CPU 71 | {84FB7387-0A7D-4B73-B724-0C79FFB821FD}.Release|x86.Build.0 = Release|Any CPU 72 | EndGlobalSection 73 | GlobalSection(SolutionProperties) = preSolution 74 | HideSolutionNode = FALSE 75 | EndGlobalSection 76 | GlobalSection(ExtensibilityGlobals) = postSolution 77 | SolutionGuid = {4D0C09DE-4056-40E4-8661-4BD5B8F2C5E5} 78 | EndGlobalSection 79 | EndGlobal 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OnionArch 2 | A .NET Core demo application which uses the Onion Architecture 3 | 4 | ## Licence Used 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | 7 | See the contents of the LICENSE file for details 8 | 9 | ## Support This Project 10 | 11 | If you have found this project helpful, either as a library that you use or as a learning tool, please consider buying me a coffee: 12 | 13 | Buy Me A Coffee 14 | 15 | ## Pull Requests 16 | 17 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 18 | 19 | Pull requests are welcome, but please take a moment to read the Code of Conduct before submitting them or commenting on any work in this repo. 20 | 21 | ## Code of Conduct 22 | OnionArch has a Code of Conduct which all contributors, maintainers and forkers must adhere to. When contributing, maintaining, forking or in any other way changing the code presented in this repository, all users must agree to this Code of Conduct. 23 | 24 | See [Code of Conduct.md](Code-of-Conduct.md) for details. 25 | 26 | ## Installing the Template 27 | 28 | To use the template locally, it will need to be installed. You can do this from either the NuGet package (see the next section) or by installing it from source. To install from source, please seen the [NuGetReadMe.md](NuGetReadme) file for instructions. 29 | 30 | ## NuGet Package 31 | 32 | This project is now available as a [NuGet package](https://www.nuget.org/packages/OnionArch.Mvc) 33 | 34 | ## Running The Application 35 | 36 | 1. Ensure that the `appsettings.json` file contains a valid `ConnectionStrings` section. 37 | 38 | You will need two connection strings: 39 | 40 | * onionDataConnection 41 | 42 | This is the database which will contain all of the Book and Series information 43 | 44 | * onionAuthConnection 45 | 46 | This is the database which will contain all of the ASP.NET MVC Core auth information. 47 | 48 | Example ConnectionStrings section: 49 | 50 | "ConnectionStrings": { 51 | "onionDataConnection": "DataSource=onionData.db", 52 | "onionAuthConnection": "DataSource=onionAuth.db" 53 | }, 54 | 55 | 2. Open a command prompt in the `Onion.Web` directory 56 | 57 | Issue the following commands to set up the databases: 58 | 59 | dotnet restore 60 | 61 | Check for migrations in the `Onion.Repo.Identity` directory. If there isn't a directory labelled `Migrations`, then run the following (from the `Onion.Web`) directory to generate them: 62 | 63 | dotnet ef migrations add CreateIdentitySchema -c AppIdentityDbContext -p ../Onion.Repo/Onion.Repo.csproj -s Onion.Web.csproj 64 | 65 | Similarly, check for migrations in the `Onion.Repo.Data` directory. If there isn't a directory labelled `Migrations`, then run the following (from the `Onion.Web`) directory to generate them: 66 | 67 | dotnet ef migrations add InitialMigration -c DataContext -p ../Onion.Repo/Onion.Repo.csproj -s Onion.Web.csproj 68 | 69 | Apply all migrations to the databases by running the following commands (from the `Onion.Web` directory): 70 | 71 | dotnet ef database update -c DataContext -p ../Onion.Repo/Onion.Repo.csproj -s Onion.Web.csproj 72 | dotnet ef database update -c AppIdentityDbContext -p ../Onion.Repo/Onion.Repo.csproj -s Onion.Web.csproj 73 | 74 | 3. Build the front end 75 | 76 | Issue the following commands from the `Onion.Web` directory: 77 | 78 | npm i 79 | npm run build 80 | 81 | 4. Run the application and seed the database 82 | 83 | Issue the following command from the `Onion.Web` directory: 84 | 85 | dotnet run 86 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | branches: 3 | only: 4 | - master 5 | init: 6 | # Good practise, because Windows line endings are different from Unix/Linux ones 7 | - cmd: git config --global core.autocrlf true 8 | install: 9 | # Install repo specific stuff here 10 | build_script: 11 | # Create a NuGet packge for uploading 12 | - cmd: nuget pack OnionArch.nuspec 13 | after_build: 14 | # For once the build has completed 15 | artifacts: 16 | - path: '\*.nupkg' 17 | name: NuGet-OwaspHeaders 18 | type: Auto 19 | clone_depth: 1 20 | deploy: 21 | provider: Environment 22 | name: NuGet-OwaspHeaders 23 | on_finish : 24 | # any cleanup in here -------------------------------------------------------------------------------- /commands.md: -------------------------------------------------------------------------------- 1 | ## Build nuget Package 2 | 3 | nuget pack OnionArch.nuspec 4 | 5 | - Assumes you are in the root of the repo 6 | - Requires `.template.config` directory to be named `template.config` 7 | - Requires all `bin` and `obj` directories to be deleted 8 | - Requires installation of nuget.exe 9 | - Including Mono on non-Windows environments 10 | 11 | ## Add to Local Template List (for testing) 12 | 13 | dotnet new --install . 14 | 15 | - Assumes you are in the root of the repo 16 | - Requires `template.config` directory to be named `.template.config` 17 | 18 | ## Remove From Local Template List (for testing) 19 | 20 | dotnet new --uninstall /home/jay/Code/OnionArch 21 | 22 | - Assumes that `/home/jay/Code/OnionArch` is the absolute path for `.template.config` directory 23 | - Requires `template.config` directory to be named `.template.config` -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 58 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /onionArch-nuget-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaProgMan/OnionArch/9c7517c3bead83a75f552f10fdcf79bed117767e/onionArch-nuget-logo.png -------------------------------------------------------------------------------- /onionArch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GaProgMan/OnionArch/9c7517c3bead83a75f552f10fdcf79bed117767e/onionArch.png -------------------------------------------------------------------------------- /template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jamie Taylor", 3 | "classifications": [ 4 | "Web", 5 | "MVC" 6 | ], 7 | "name": "Onion Architecture MVC", 8 | "identity": "OnionArch.Mvc", 9 | "shortName": "onionArch", 10 | "tags": { 11 | "language": "C#", 12 | "type": "project" 13 | }, 14 | "sourceName": "Onion", 15 | "preferNameDirectory": true, 16 | "sources": [ 17 | { 18 | "exclude": [ 19 | ".idea/**", 20 | ".vscode/**", 21 | "obj/**", 22 | "bin/**", 23 | ".template.config/**", 24 | "*.nuspec", 25 | "*.nupkg", 26 | "*.yml", 27 | "*.png", 28 | "*.svg", 29 | ".git/**" 30 | ] 31 | } 32 | ], 33 | "symbols": { 34 | "enable-gnu-pratchett": { 35 | "type": "parameter", 36 | "dataType": "bool", 37 | "defaultValue": "false", 38 | "description": "Whether to include and activate middleware which will include the X-GNU-Pratchett header in all requests" 39 | }, 40 | "enable-secure-headers": { 41 | "type": "parameter", 42 | "dataType": "bool", 43 | "defaultValue": "false", 44 | "description": "Whether to include and activate middleware which will include a range of OWASP suggested security headers" 45 | } 46 | } 47 | } --------------------------------------------------------------------------------