├── .gitignore ├── README.md ├── netcore-bootstrap.sln ├── src ├── Controllers │ ├── AccountController.cs │ ├── HomeController.cs │ ├── UserManagementController.cs │ └── api │ │ └── v1 │ │ ├── AccountApiController.cs │ │ └── HomeApiController.cs ├── Dockerfile ├── Dockerrun.aws.json ├── Helpers │ └── AccountHelper.cs ├── LICENSE.md ├── Mail │ ├── IMailer.cs │ └── Mailer.cs ├── Migrations │ ├── 20180226200614_InitialMigration.Designer.cs │ ├── 20180226200614_InitialMigration.cs │ └── DataBaseContextModelSnapshot.cs ├── Models │ ├── Database │ │ ├── BaseEntity.cs │ │ └── User.cs │ ├── VOs │ │ └── UserVO.cs │ └── Views │ │ ├── LoginViewModel.cs │ │ ├── RoleManagerViewModel.cs │ │ ├── UserManagementViewModel.cs │ │ └── UserViewModel.cs ├── NetCoreBootstrap.csproj ├── Program.cs ├── Repositories │ ├── Database │ │ ├── DataBaseContext.cs │ │ └── UnitOfWork.cs │ ├── Interfaces │ │ ├── IRepository.cs │ │ ├── IUnitOfWork.cs │ │ └── IUserRepository.cs │ ├── Repository.cs │ └── UserRepository.cs ├── Resources │ ├── en-US.json │ └── es-AR.json ├── Scripts │ ├── Src │ │ └── Program.cs │ ├── appsettings.Development.json │ ├── bootstrap-script.deps.json │ ├── bootstrap-script.dll │ ├── bootstrap-script.pdb │ ├── bootstrap-script.runtimeconfig.dev.json │ ├── bootstrap-script.runtimeconfig.json │ ├── delete_script.sh │ └── script.sh ├── Startup.cs ├── Views │ ├── Account │ │ ├── AccessDenied.cshtml │ │ ├── Edit.cshtml │ │ ├── Login.cshtml │ │ └── Register.cshtml │ ├── Home │ │ ├── About.cshtml │ │ ├── Contact.cshtml │ │ └── Index.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _Layout.cshtml │ │ ├── _LoginPartial.cshtml │ │ └── _UserManagementPartial.cshtml │ ├── UserManagement │ │ ├── AddRole.cshtml │ │ ├── CreateRole.cshtml │ │ ├── EditRole.cshtml │ │ ├── RoleManager.cshtml │ │ ├── Roles.cshtml │ │ ├── Users.cshtml │ │ ├── _EditRoleForm.cshtml │ │ └── _UserRoles.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── appsettings.json ├── assets │ └── styles │ │ ├── example-site.min.scss │ │ ├── example-site.scss │ │ └── site.scss ├── bower.json ├── bundleconfig.json ├── ca.ruleset ├── gulpfile.js ├── package-lock.json ├── package.json └── wwwroot │ ├── css │ └── site.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 ├── test ├── Controllers │ └── api │ │ └── v1 │ │ └── HomeApiTestController.cs └── NetCoreBootstrap.Tests.csproj └── tools └── tools.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | obj 3 | bin 4 | node_modules 5 | wwwroot/css 6 | /src/appsettings.Development.json 7 | Logs 8 | coverage-html 9 | coverage.json 10 | coverage-hits.txt 11 | -------------------------------------------------------------------------------- /netcore-bootstrap.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCoreBootstrap", "src\NetCoreBootstrap.csproj", "{53F54B87-3B3B-4C06-9913-E9E96CA961B6}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCoreBootstrap.Tests", "test\NetCoreBootstrap.Tests.csproj", "{320BBEED-35C3-445B-85BE-8DFA3D9A97B2}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Debug|x64.ActiveCfg = Debug|x64 26 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Debug|x64.Build.0 = Debug|x64 27 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Debug|x86.ActiveCfg = Debug|x86 28 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Debug|x86.Build.0 = Debug|x86 29 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Release|x64.ActiveCfg = Release|x64 32 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Release|x64.Build.0 = Release|x64 33 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Release|x86.ActiveCfg = Release|x86 34 | {53F54B87-3B3B-4C06-9913-E9E96CA961B6}.Release|x86.Build.0 = Release|x86 35 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Debug|x64.ActiveCfg = Debug|x64 38 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Debug|x64.Build.0 = Debug|x64 39 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Debug|x86.ActiveCfg = Debug|x86 40 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Debug|x86.Build.0 = Debug|x86 41 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Release|x64.ActiveCfg = Release|x64 44 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Release|x64.Build.0 = Release|x64 45 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Release|x86.ActiveCfg = Release|x86 46 | {320BBEED-35C3-445B-85BE-8DFA3D9A97B2}.Release|x86.Build.0 = Release|x86 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /src/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Authorization; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.EntityFrameworkCore; 11 | using Microsoft.Extensions.Options; 12 | using NetCoreBootstrap.Models.Database; 13 | using NetCoreBootstrap.Models.Views; 14 | using NetCoreBootstrap.Repositories; 15 | 16 | namespace NetCoreBootstrap.Controllers 17 | { 18 | [Route("[controller]")] 19 | public class AccountController : Controller 20 | { 21 | private readonly UserManager _userManager; 22 | private readonly SignInManager _signInManager; 23 | 24 | public AccountController(UserManager userManager, SignInManager signInManager) 25 | { 26 | this._userManager = userManager; 27 | this._signInManager = signInManager; 28 | } 29 | 30 | public SignInManager SignInManager { get => this._signInManager; } 31 | 32 | public UserManager UserManager { get => this._userManager; } 33 | 34 | [AllowAnonymous] 35 | [HttpGet("Register")] 36 | public IActionResult Register() => View(); 37 | 38 | [AllowAnonymous] 39 | [HttpPost("Register")] 40 | public async Task Register(UserViewModel userViewModel) 41 | { 42 | if (ModelState.IsValid) 43 | { 44 | var user = new User { UserName = userViewModel.UserName, Email = userViewModel.Email, IsExternal = false }; 45 | var result = await UserManager.CreateAsync(user, userViewModel.Password); 46 | if (result.Succeeded) 47 | { 48 | await SignInManager.SignInAsync(user, true); 49 | return RedirectToAction("Users", "UserManagement"); 50 | } 51 | else foreach (var error in result.Errors) ModelState.AddModelError(string.Empty, error.Description); 52 | } 53 | return View(userViewModel); 54 | } 55 | 56 | [AllowAnonymous] 57 | [HttpGet("Login")] 58 | public async Task Login() 59 | { 60 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); 61 | var viewModel = new LoginViewModel(); 62 | viewModel.LoginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList(); 63 | return View(viewModel); 64 | } 65 | 66 | [AllowAnonymous] 67 | [HttpPost("Login")] 68 | public async Task Login(LoginViewModel loginViewModel) 69 | { 70 | if (ModelState.IsValid) 71 | { 72 | var result = await SignInManager.PasswordSignInAsync(loginViewModel.UserName, loginViewModel.Password, loginViewModel.RememberMe, false); 73 | if (result.Succeeded) return RedirectToAction("Users", "UserManagement"); 74 | ModelState.AddModelError(string.Empty, "Invalid login attempt."); 75 | } 76 | loginViewModel.LoginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList(); 77 | return View(loginViewModel); 78 | } 79 | 80 | [AllowAnonymous] 81 | [HttpPost("ExternalLogin")] 82 | public IActionResult ExternalLogin(string provider, string returnUrl = null) 83 | { 84 | // Request a redirect to the external login provider. 85 | var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { ReturnUrl = returnUrl }); 86 | var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); 87 | return Challenge(properties, provider); 88 | } 89 | 90 | [AllowAnonymous] 91 | [HttpGet("ExternalLoginCallback")] 92 | public async Task ExternalLoginCallback(string returnUrl = null, string remoteError = null) 93 | { 94 | if (remoteError != null) 95 | { 96 | ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}"); 97 | return View(nameof(Login)); 98 | } 99 | var info = await SignInManager.GetExternalLoginInfoAsync(); 100 | if (info == null) 101 | { 102 | return RedirectToAction("Login"); 103 | } 104 | 105 | var user = await UserManager.FindByEmailAsync(info.Principal.FindFirstValue(ClaimTypes.Email)); 106 | if (user != null && !user.IsExternal) 107 | { 108 | // Already exists a local user with the external login mail. 109 | throw new Exception($"This email {user.Email} is already registered."); 110 | } 111 | 112 | // Sign in the user with this external login provider if the user already has a login. 113 | var result = await SignInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); 114 | if (result.Succeeded) return RedirectToAction("Users", "UserManagement"); 115 | else if (result.IsLockedOut || result.IsNotAllowed || result.RequiresTwoFactor) throw new Exception(result.ToString()); 116 | else if (await ConfirmExternalLogin(new UserManagementViewModel { Email = info.Principal.FindFirstValue(ClaimTypes.Email) })) 117 | return RedirectToAction("Users", "UserManagement"); 118 | else return RedirectToAction("Login"); 119 | } 120 | 121 | [HttpGet("Edit")] 122 | [Authorize] 123 | public async Task Edit() 124 | { 125 | var user = await UserManager.GetUserAsync(HttpContext.User); 126 | if (user.IsExternal) return RedirectToAction("AccessDenied"); 127 | var userViewModel = new UserManagementViewModel { UserId = user.Id }; 128 | return View(userViewModel); 129 | } 130 | 131 | [HttpPost("Edit")] 132 | [Authorize] 133 | public async Task Edit(UserManagementViewModel viewModel) 134 | { 135 | var user = await UserManager.GetUserAsync(HttpContext.User); 136 | if (ModelState.IsValid) 137 | { 138 | var result = await UserManager.ChangePasswordAsync(user, viewModel.Password, viewModel.NewPassword); 139 | if (result.Succeeded) return RedirectToAction("Users", "UserManagement"); 140 | foreach (var error in result.Errors) ModelState.AddModelError(error.Code, error.Description); 141 | } 142 | return View(viewModel); 143 | } 144 | 145 | [HttpPost("Logout")] 146 | public async Task Logout() 147 | { 148 | await SignInManager.SignOutAsync(); 149 | return RedirectToAction("Index", "Home"); 150 | } 151 | 152 | [HttpGet("AccessDenied")] 153 | public IActionResult AccessDenied() => View(); 154 | 155 | private async Task ConfirmExternalLogin(UserManagementViewModel viewModel) 156 | { 157 | var info = await SignInManager.GetExternalLoginInfoAsync(); 158 | if (info == null) return false; 159 | var user = new User { UserName = viewModel.Email, Email = viewModel.Email, IsExternal = true }; 160 | var result = await UserManager.CreateAsync(user); 161 | if (result.Succeeded) 162 | { 163 | result = await UserManager.AddLoginAsync(user, info); 164 | if (result.Succeeded) 165 | { 166 | await SignInManager.SignInAsync(user, isPersistent: false); 167 | return true; 168 | } 169 | } 170 | return false; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Localization; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.Extensions.Logging; 6 | using NetCoreBootstrap.Models.Database; 7 | using NetCoreBootstrap.Repositories; 8 | using NetCoreBootstrap.Repositories.Database; 9 | 10 | namespace NetCoreBootstrap.Controllers 11 | { 12 | public class HomeController : Controller 13 | { 14 | private readonly IHtmlLocalizer _localizer; 15 | private readonly ILogger _log; 16 | 17 | public HomeController(DbContextOptions options, IHtmlLocalizer localizer, ILogger log) 18 | { 19 | this._localizer = localizer; 20 | this._log = log; 21 | } 22 | 23 | public IHtmlLocalizer Localizer { get => this._localizer; } 24 | 25 | public ILogger Log { get => this._log; } 26 | 27 | [HttpGet("")] 28 | public IActionResult Index() 29 | { 30 | return View(); 31 | } 32 | 33 | [HttpGet("About")] 34 | public IActionResult About() 35 | { 36 | ViewData["Message"] = Localizer["DescriptionPage"]; 37 | return View(); 38 | } 39 | 40 | [HttpGet("Contact")] 41 | public IActionResult Contact() 42 | { 43 | ViewData["Message"] = Localizer["ContactPage"]; 44 | return View(); 45 | } 46 | 47 | [HttpGet("Error")] 48 | public IActionResult Error() 49 | { 50 | return View(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Controllers/UserManagementController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.Localization; 7 | using Microsoft.EntityFrameworkCore; 8 | using NetCoreBootstrap.Models.Database; 9 | using NetCoreBootstrap.Models.Views; 10 | using NetCoreBootstrap.Repositories; 11 | using NetCoreBootstrap.Repositories.Database; 12 | using NetCoreBootstrap.Repositories.Interfaces; 13 | 14 | namespace NetCoreBootstrap.Controllers 15 | { 16 | [Route("[controller]")] 17 | [Authorize] 18 | public class UserManagementController : Controller 19 | { 20 | private readonly IHtmlLocalizer _localizer; 21 | private readonly IUnitOfWork _unitOfWork; 22 | 23 | public UserManagementController(DataBaseContext context, 24 | UserManager userManager, 25 | RoleManager roleManager, 26 | IHtmlLocalizer localizer) 27 | { 28 | this._localizer = localizer; 29 | this._unitOfWork = new UnitOfWork(context, userManager, roleManager); 30 | } 31 | 32 | public IUnitOfWork UnitOfWork { get => this._unitOfWork; } 33 | 34 | public IHtmlLocalizer Localizer { get => this._localizer; } 35 | 36 | [HttpGet("Users")] 37 | public IActionResult Users() 38 | { 39 | return View(new UserManagementViewModel { Users = UnitOfWork.UserRepository.GetAllUsersWithRoles() }); 40 | } 41 | 42 | [HttpGet("Roles")] 43 | public IActionResult Roles() => View(new UserManagementViewModel { Roles = UnitOfWork.UserRepository.GetAllRoles() }); 44 | 45 | [HttpGet("NewRole")] 46 | public IActionResult CreateRole() => View(); 47 | 48 | [HttpPost("NewRole")] 49 | public async Task CreateRole(UserManagementViewModel viewModel) 50 | { 51 | if (ModelState.IsValid) 52 | { 53 | var result = await UnitOfWork.UserRepository.CreateRole(viewModel.Name); 54 | if (result.Succeeded) return RedirectToAction("Roles"); 55 | foreach (var error in result.Errors) ModelState.AddModelError(error.Code, error.Description); 56 | } 57 | return View(viewModel); 58 | } 59 | 60 | [HttpGet("DeleteRole")] 61 | public async Task DeleteRole(string roleId) 62 | { 63 | var result = await UnitOfWork.UserRepository.DeleteRole(roleId); 64 | if (result) ViewData["Message"] = Localizer["RoleDeleted"]; 65 | else ViewData["Message"] = Localizer["RoleNotDeleted"]; 66 | return View("./Views/UserManagement/Roles.cshtml", new UserManagementViewModel { Roles = UnitOfWork.UserRepository.GetAllRoles() }); 67 | } 68 | 69 | [HttpGet("EditRole")] 70 | public async Task EditRole(string roleId) 71 | { 72 | var role = await UnitOfWork.UserRepository.GetRoleById(roleId); 73 | var roleViewModel = new UserManagementViewModel { RoleId = role.Id, Name = role.Name }; 74 | return View(roleViewModel); 75 | } 76 | 77 | [HttpPost("EditRole")] 78 | public async Task EditRole(UserManagementViewModel viewModel) 79 | { 80 | var result = await UnitOfWork.UserRepository.UpdateRole(viewModel.RoleId, viewModel.Name); 81 | if (result) ViewData["Message"] = Localizer["RoleUpdated"]; 82 | else ViewData["Message"] = Localizer["RoleNotUpdated"]; 83 | return View("./Views/UserManagement/Roles.cshtml", new UserManagementViewModel { Roles = UnitOfWork.UserRepository.GetAllRoles() }); 84 | } 85 | 86 | [HttpGet("RoleManager")] 87 | public IActionResult RoleManager() 88 | { 89 | return View(new RoleManagerViewModel { UsersListItem = UnitOfWork.UserRepository.GetUsersListItem(), Roles = new Dictionary() }); 90 | } 91 | 92 | [HttpGet("ViewRoles")] 93 | public async Task ViewRoles(string userId) 94 | { 95 | var viewModel = new RoleManagerViewModel { Roles = new Dictionary() }; 96 | var user = await UnitOfWork.UserRepository.GetUserById(userId); 97 | foreach (var role in UnitOfWork.UserRepository.GetAllRoles()) 98 | { 99 | viewModel.Roles[role.ToString()] = await UnitOfWork.UserRepository.IsUserInRole(user, role.ToString()); 100 | } 101 | viewModel.SelectedUserId = userId; 102 | return PartialView("_UserRoles", viewModel); 103 | } 104 | 105 | [HttpPost("AddRolesToUser")] 106 | public async Task AddRolesToUser(RoleManagerViewModel viewModel) 107 | { 108 | var user = await UnitOfWork.UserRepository.GetUserById(viewModel.SelectedUserId); 109 | foreach (var role in viewModel.Roles) 110 | { 111 | if (await UnitOfWork.UserRepository.IsUserInRole(user, role.Key) && !role.Value) await UnitOfWork.UserRepository.RemoveRoleFromUser(user, role.Key); 112 | else if (role.Value) await UnitOfWork.UserRepository.AddRoleToUser(user, role.Key); 113 | } 114 | return RedirectToAction("RoleManager"); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Controllers/api/v1/AccountApiController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IdentityModel.Tokens.Jwt; 4 | using System.Linq; 5 | using System.Security.Claims; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Web; 9 | using Microsoft.AspNetCore.Authentication.JwtBearer; 10 | using Microsoft.AspNetCore.Authorization; 11 | using Microsoft.AspNetCore.Http; 12 | using Microsoft.AspNetCore.Identity; 13 | using Microsoft.AspNetCore.Mvc; 14 | using Microsoft.AspNetCore.Mvc.Localization; 15 | using Microsoft.Extensions.Configuration; 16 | using Microsoft.IdentityModel.Tokens; 17 | using NetCoreBootstrap.Helpers; 18 | using NetCoreBootstrap.Mail; 19 | using NetCoreBootstrap.Models.Database; 20 | using NetCoreBootstrap.Models.Views; 21 | using NetCoreBootstrap.Models.VOs; 22 | using NetCoreBootstrap.Repositories.Interfaces; 23 | 24 | namespace NetCoreBootstrap.Api.V1.Controllers 25 | { 26 | [Route("/api/v1/[controller]/[action]")] 27 | public class AccountApiController : Controller 28 | { 29 | private readonly SignInManager _signInManager; 30 | private readonly UserManager _userManager; 31 | private readonly IConfiguration _configuration; 32 | private readonly IMailer _mailer; 33 | private readonly IHtmlLocalizer _localizer; 34 | private readonly IUnitOfWork _unitOfWork; 35 | 36 | public AccountApiController(UserManager userManager, 37 | SignInManager signInManager, 38 | IUnitOfWork unitOfWork, 39 | IConfiguration configuration, 40 | IMailer mailer, 41 | IHtmlLocalizer localizer) 42 | { 43 | this._userManager = userManager; 44 | this._signInManager = signInManager; 45 | this._configuration = configuration; 46 | this._mailer = mailer; 47 | this._localizer = localizer; 48 | this._unitOfWork = unitOfWork; 49 | } 50 | 51 | public UserManager UserManager { get => this._userManager; } 52 | public SignInManager SignInManager { get => this._signInManager; } 53 | public IConfiguration Configuration { get => this._configuration; } 54 | public IMailer Mailer { get => this._mailer; } 55 | public IHtmlLocalizer Localizer { get => this._localizer; } 56 | public IUnitOfWork UnitOfWork { get => this._unitOfWork; } 57 | 58 | [HttpPost("SignIn")] 59 | public async Task SignIn([FromBody] UserVO userValueObject) 60 | { 61 | object response; 62 | try 63 | { 64 | if (!string.IsNullOrEmpty(userValueObject.Email)) userValueObject.Email = userValueObject.Email.ToLower(); 65 | var result = await SignInManager.PasswordSignInAsync(userValueObject.Email, userValueObject.Password, userValueObject.RememberMe, false); 66 | if (result.Succeeded) 67 | { 68 | var appUser = UserManager.Users.SingleOrDefault(r => r.Email == userValueObject.Email); 69 | Response.StatusCode = StatusCodes.Status200OK; 70 | var configVariables = new Dictionary 71 | { 72 | { "key", Configuration["Jwt:Key"] }, 73 | { "expire", Configuration["Jwt:ExpireDays"] }, 74 | { "issuer", Configuration["Jwt:Issuer"] }, 75 | }; 76 | response = new UserVO 77 | { 78 | Token = $"Bearer {AccountHelper.GenerateJwtToken(userValueObject.Email, appUser, configVariables)}", 79 | Email = appUser.Email, 80 | }; 81 | } 82 | else if (result.IsNotAllowed) 83 | { 84 | Response.StatusCode = StatusCodes.Status401Unauthorized; 85 | response = new { Message = Localizer["account_login_confirm_email"].Value }; 86 | } 87 | else 88 | { 89 | Response.StatusCode = StatusCodes.Status400BadRequest; 90 | if (string.IsNullOrEmpty(userValueObject.Email) || string.IsNullOrEmpty(userValueObject.Password)) 91 | response = new { Message = Localizer["account_login_failed_empty_fields"].Value }; 92 | else 93 | response = new { Message = Localizer["account_login_failed"].Value }; 94 | } 95 | return Json(response); 96 | } 97 | catch (Exception e) 98 | { 99 | Response.StatusCode = StatusCodes.Status400BadRequest; 100 | return Json(new { Message = e.Message }); 101 | } 102 | } 103 | 104 | [HttpPost("ExternalSignIn")] 105 | public async Task ExternalSignIn([FromBody] UserVO userValueObject) 106 | { 107 | object response; 108 | string provider = userValueObject.IsFacebook ? "Facebook" : "Google"; 109 | IdentityResult result = null; 110 | User user = null; 111 | if (!UserManager.Users.Any(u => u.Email == userValueObject.Email)) 112 | { 113 | result = await UserManager.CreateAsync(new User 114 | { 115 | Email = userValueObject.Email, 116 | UserName = userValueObject.Email, 117 | EmailConfirmed = true, 118 | IsExternal = true, 119 | }); 120 | user = await UserManager.FindByEmailAsync(userValueObject.Email); 121 | await UserManager.AddLoginAsync(user, new UserLoginInfo(provider, userValueObject.UserId, user.Email)); 122 | } 123 | if (result == null || result.Succeeded) 124 | { 125 | if (user == null) user = await UserManager.FindByEmailAsync(userValueObject.Email); 126 | var signInResult = await SignInManager.ExternalLoginSignInAsync(provider, userValueObject.UserId, false); 127 | if (signInResult.Succeeded) 128 | { 129 | Response.StatusCode = StatusCodes.Status200OK; 130 | var configVariables = new Dictionary 131 | { 132 | { "key", Configuration["Jwt:Key"] }, 133 | { "expire", Configuration["Jwt:ExpireDays"] }, 134 | { "issuer", Configuration["Jwt:Issuer"] }, 135 | }; 136 | response = new { Token = $"Bearer {AccountHelper.GenerateJwtToken(userValueObject.Email, user, configVariables)}" }; 137 | } 138 | else 139 | { 140 | string message = Localizer["account_external_login_failed"].Value; 141 | var logins = await UserManager.GetLoginsAsync(user); 142 | if (!logins.Any(login => login.LoginProvider == provider)) 143 | message = Localizer["account_external_login_invalid_provider"].Value + $"{provider}"; 144 | else if (!logins.Any(login => login.ProviderKey == userValueObject.UserId)) 145 | message = Localizer["account_external_login_invalid_user_id"].Value; 146 | Response.StatusCode = StatusCodes.Status400BadRequest; 147 | response = new { Message = message, Errors = string.Join(";", signInResult.ToString()) }; 148 | } 149 | } 150 | else 151 | { 152 | Response.StatusCode = StatusCodes.Status400BadRequest; 153 | response = new { Message = Localizer["account_external_login_failed"].Value, Errors = string.Join(";", result.Errors) }; 154 | } 155 | return Json(response); 156 | } 157 | 158 | [HttpPost("SignUp")] 159 | public async Task SignUp([FromBody] UserVO userValueObject) 160 | { 161 | var user = new User 162 | { 163 | UserName = userValueObject.Email.ToLower(), 164 | Email = userValueObject.Email.ToLower(), 165 | }; 166 | object response; 167 | if (!user.IsEmailValid()) 168 | { 169 | Response.StatusCode = StatusCodes.Status400BadRequest; 170 | response = new { Message = Localizer["account_user_not_created_invalid_email"].Value }; 171 | } 172 | else if (userValueObject.Password != userValueObject.ConfirmPassword) 173 | { 174 | Response.StatusCode = StatusCodes.Status400BadRequest; 175 | response = new { Message = Localizer["account_user_not_created_password_did_not_match"].Value }; 176 | } 177 | else 178 | { 179 | try 180 | { 181 | var result = await UserManager.CreateAsync(user, userValueObject.Password); 182 | if (result.Succeeded) 183 | { 184 | await SendConfirmationEmailAsync(user); 185 | Response.StatusCode = StatusCodes.Status200OK; 186 | response = new { Message = Localizer["account_user_created"].Value }; 187 | } 188 | else 189 | { 190 | Response.StatusCode = StatusCodes.Status400BadRequest; 191 | response = new { Message = $"{Localizer["account_user_not_created"].Value}{result.Errors.Select(e => e.Description).Last()}" }; 192 | } 193 | } 194 | catch (ArgumentNullException e) 195 | { 196 | Response.StatusCode = StatusCodes.Status400BadRequest; 197 | response = new { Message = $"{Localizer["account_user_not_created"].Value}{e.Message}" }; 198 | } 199 | } 200 | return Json(response); 201 | } 202 | 203 | [HttpGet("ConfirmEmail/{userId}/{token}")] 204 | public async Task ConfirmEmail(string userId, string token) 205 | { 206 | var user = UserManager.FindByIdAsync(userId).Result; 207 | var result = await UserManager.ConfirmEmailAsync(user, HttpUtility.UrlDecode(token)); 208 | object response; 209 | if (result.Succeeded) 210 | { 211 | Response.StatusCode = StatusCodes.Status200OK; 212 | response = new { Message = Localizer["account_email_confirmed"].Value }; 213 | } 214 | else 215 | { 216 | Response.StatusCode = StatusCodes.Status400BadRequest; 217 | response = new { Message = Localizer["account_email_not_confirmed"].Value, Errors = result.Errors }; 218 | } 219 | return Json(response); 220 | } 221 | 222 | [HttpPost("ForgotPassword")] 223 | public async Task ForgotPassword([FromBody] UserVO userValueObject) 224 | { 225 | object response; 226 | var user = await _userManager.FindByEmailAsync(userValueObject.Email); 227 | if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) 228 | { 229 | Response.StatusCode = StatusCodes.Status400BadRequest; 230 | response = new { Message = Localizer["account_forgot_password_user_not_found"].Value }; 231 | } 232 | else 233 | { 234 | await SendRecoveryPasswordEmailAsync(user); 235 | Response.StatusCode = StatusCodes.Status200OK; 236 | response = new { Message = Localizer["account_forgot_password_email_sent"].Value }; 237 | } 238 | return Json(response); 239 | } 240 | 241 | [HttpPost("EditAccount")] 242 | [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 243 | public async Task EditAccount([FromBody] UserVO userValueObject) 244 | { 245 | var user = await UserManager.FindByEmailAsync(User.Identity.Name); 246 | object response; 247 | if (user == null || !User.Identity.IsAuthenticated) 248 | { 249 | Response.StatusCode = StatusCodes.Status400BadRequest; 250 | response = new { Message = Localizer["account_user_not_authenticated"].Value }; 251 | } 252 | else if (userValueObject.NewPassword != userValueObject.ConfirmPassword) 253 | { 254 | Response.StatusCode = StatusCodes.Status400BadRequest; 255 | response = new { Message = Localizer["account_user_not_created_password_did_not_match"].Value }; 256 | } 257 | else 258 | { 259 | var result = await UserManager.ChangePasswordAsync(user, userValueObject.Password, userValueObject.NewPassword); 260 | if (result.Succeeded) 261 | { 262 | Response.StatusCode = StatusCodes.Status200OK; 263 | response = new { Message = Localizer["account_password_changed"].Value }; 264 | } 265 | else 266 | { 267 | Response.StatusCode = StatusCodes.Status400BadRequest; 268 | response = new { Message = $"{Localizer["account_password_could_not_be_changed"].Value}: {string.Join(" ", result.Errors.Select(err => err.Description))}" }; 269 | } 270 | } 271 | return Json(response); 272 | } 273 | 274 | [HttpGet("ResetPassword/{userId}/{token}")] 275 | public async Task ResetPassword(string userId, string token) 276 | { 277 | var user = await UserManager.FindByIdAsync(userId); 278 | var newPassword = AccountHelper.GenerateRandomPassword(8); 279 | var result = await UserManager.ResetPasswordAsync(user, HttpUtility.UrlDecode(token), newPassword); 280 | object response; 281 | if (result.Succeeded) 282 | { 283 | SendNewPasswordEmailAsync(user, newPassword); 284 | Response.StatusCode = StatusCodes.Status200OK; 285 | response = new { Message = Localizer["account_new_password_confirmed"].Value }; 286 | } 287 | else 288 | { 289 | Response.StatusCode = StatusCodes.Status400BadRequest; 290 | response = new { Message = Localizer["account_new_password_not_confirmed"].Value, Errors = result.Errors }; 291 | } 292 | return Json(response); 293 | } 294 | 295 | private void SendNewPasswordEmailAsync(User user, string newPassword) 296 | { 297 | var subject = Localizer["account_new_password_email_subject"].Value; 298 | var body = Localizer["account_new_password_email_body"].Value + $": {newPassword}"; 299 | Mailer.SendMail(user.Email, subject, body); 300 | } 301 | 302 | private async Task SendConfirmationEmailAsync(User user) 303 | { 304 | var token = await UserManager.GenerateEmailConfirmationTokenAsync(user); 305 | var tokenHtml = HttpUtility.UrlEncode(token); 306 | var callbackUrl = Configuration["AppUrl"] + this.Url.Action("ConfirmEmail", "AccountApi", new { userId = user.Id, token = tokenHtml }); 307 | var subject = Localizer["account_email_subject"].Value; 308 | var body = Localizer["account_email_body"].Value + $" here."; 309 | Mailer.SendMail(user.Email, subject, body); 310 | } 311 | 312 | private async Task SendRecoveryPasswordEmailAsync(User user) 313 | { 314 | var token = await _userManager.GeneratePasswordResetTokenAsync(user); 315 | var tokenHtml = HttpUtility.UrlEncode(token); 316 | var callbackUrl = Configuration["AppUrl"] + this.Url.Action("ResetPassword", "AccountApi", new { userId = user.Id, token = tokenHtml }); 317 | var subject = Localizer["account_forgot_password_email_subject"].Value; 318 | var body = Localizer["account_forgot_password_email_body"].Value + $" here."; 319 | Mailer.SendMail(user.Email, subject, body); 320 | } 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /src/Controllers/api/v1/HomeApiController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace NetCoreBootstrap.Controllers.Api.V1 8 | { 9 | [Route("api/v1/[controller]")] 10 | public class HomeApiController : Controller 11 | { 12 | // GET api/v1/homeapi/{id} 13 | [HttpGet("{id}")] 14 | public int Get(int id) 15 | { 16 | return id; 17 | } 18 | 19 | // POST api/v1/homeapi 20 | [HttpPost] 21 | public void Post([FromBody]string value) 22 | { 23 | } 24 | 25 | // PUT api/v1/homeapi/5 26 | [HttpPut("{id}")] 27 | public void Put(int id, [FromBody]string value) 28 | { 29 | } 30 | 31 | // DELETE api/v1/homeapi/5 32 | [HttpDelete("{id}")] 33 | public void Delete(int id) 34 | { 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-sdk 2 | WORKDIR /app 3 | EXPOSE 80 4 | 5 | # Copy csproj and restore as distinct layers 6 | COPY *.csproj ./ 7 | RUN dotnet restore 8 | 9 | # Copy and build everything else 10 | COPY . ./ 11 | RUN dotnet publish -c Release -o out 12 | CMD ASPNETCORE_URLS=http://*:$PORT dotnet out/NetCoreBootstrap.dll 13 | -------------------------------------------------------------------------------- /src/Dockerrun.aws.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSEBDockerrunVersion": "1", 3 | "Ports": [{ 4 | "ContainerPort": "80" 5 | }], 6 | "Volumes": [], 7 | "Logging": "/var/log/nginx" 8 | } 9 | -------------------------------------------------------------------------------- /src/Helpers/AccountHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IdentityModel.Tokens.Jwt; 4 | using System.Security.Claims; 5 | using System.Security.Principal; 6 | using System.Text; 7 | using NetCoreBootstrap.Models.Database; 8 | using Microsoft.IdentityModel.Tokens; 9 | 10 | namespace NetCoreBootstrap.Helpers 11 | { 12 | public static class AccountHelper 13 | { 14 | public static string GenerateRandomPassword(int length = 8) 15 | { 16 | if (length < 6) throw new ArgumentException(); 17 | string characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!$+<[%,=]>*+-/)(¡?¿·#¬|"; 18 | StringBuilder result = new StringBuilder(); 19 | result.Append(GetRandomValueFromSequence(characters, 0, characters.IndexOf("A") - 1)); 20 | result.Append(GetRandomValueFromSequence(characters, characters.IndexOf("A"), characters.IndexOf("0") - 1)); 21 | result.Append(GetRandomValueFromSequence(characters, characters.IndexOf("0"), characters.IndexOf("!") - 1)); 22 | result.Append(GetRandomValueFromSequence(characters, characters.IndexOf("!"), characters.Length)); 23 | while (result.Length < length) 24 | { 25 | result.Append(GetRandomValueFromSequence(characters, 0, characters.Length)); 26 | } 27 | return result.ToString(); 28 | } 29 | 30 | public static string GenerateJwtToken(string email, User user, Dictionary configVariables) 31 | { 32 | var claims = new ClaimsIdentity(new GenericIdentity(user.Email, "Token"), new[] { new Claim("ID", user.Id.ToString()) }); 33 | var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(configVariables["key"])); 34 | var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); 35 | var expires = DateTime.Now.AddDays(Convert.ToDouble(configVariables["expire"])); 36 | var handler = new JwtSecurityTokenHandler(); 37 | var securityToken = handler.CreateToken(new SecurityTokenDescriptor 38 | { 39 | Issuer = configVariables["issuer"], 40 | Audience = configVariables["issuer"], 41 | SigningCredentials = creds, 42 | Subject = claims, 43 | Expires = expires, 44 | }); 45 | return handler.WriteToken(securityToken); 46 | } 47 | 48 | private static string GetRandomValueFromSequence(string sequence, int minIndex, int maxIndex) 49 | { 50 | var random = new Random(); 51 | return sequence[random.Next(minIndex, maxIndex)].ToString(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Wolox 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 | -------------------------------------------------------------------------------- /src/Mail/IMailer.cs: -------------------------------------------------------------------------------- 1 | namespace NetCoreBootstrap.Mail 2 | { 3 | public interface IMailer 4 | { 5 | void SendMail(string toAddress, string subject, string body, bool isHtml = true); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Mail/Mailer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Mail; 5 | using System.Text; 6 | using Microsoft.Extensions.Configuration; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | 10 | namespace NetCoreBootstrap.Mail 11 | { 12 | public class Mailer : IMailer 13 | { 14 | private string _host, _username, _password, _name, _email; 15 | private int _hostPort; 16 | 17 | public Mailer(IConfiguration configuration) 18 | { 19 | _host = configuration["Mailer:Host"]; 20 | _hostPort = Convert.ToInt32(configuration["Mailer:Port"]); 21 | _username = configuration["Mailer:Username"]; 22 | _password = configuration["Mailer:Password"]; 23 | _name = configuration["Mailer:Name"]; 24 | _email = configuration["Mailer:Email"]; 25 | } 26 | 27 | public string Host 28 | { 29 | get { return _host; } 30 | } 31 | 32 | public int HostPort 33 | { 34 | get { return _hostPort; } 35 | } 36 | 37 | public string Username 38 | { 39 | get { return _username; } 40 | } 41 | 42 | public string Password 43 | { 44 | get { return _password; } 45 | } 46 | 47 | public string Name 48 | { 49 | get { return _name; } 50 | } 51 | 52 | public string Email 53 | { 54 | get { return _email; } 55 | } 56 | 57 | public void SendMail(string toAddress, string subject, string body, bool isHtml = true) 58 | { 59 | SmtpClient client = new SmtpClient(Host, HostPort) 60 | { 61 | UseDefaultCredentials = false, 62 | Credentials = new NetworkCredential(Username, Password), 63 | EnableSsl = true, 64 | }; 65 | MailMessage mailMessage = new MailMessage() 66 | { 67 | From = new MailAddress(Email, Name), 68 | Body = body, 69 | Subject = subject, 70 | IsBodyHtml = isHtml, 71 | }; 72 | mailMessage.To.Add(toAddress); 73 | client.Send(mailMessage); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Migrations/20180226200614_InitialMigration.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using NetCoreBootstrap.Repositories.Database; 7 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 8 | 9 | namespace NetCoreBootstrap.Migrations 10 | { 11 | [DbContext(typeof(DataBaseContext))] 12 | [Migration("20180226200614_InitialMigration")] 13 | partial class InitialMigration 14 | { 15 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder 19 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) 20 | .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); 21 | 22 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 23 | { 24 | b.Property("Id") 25 | .ValueGeneratedOnAdd(); 26 | 27 | b.Property("ConcurrencyStamp") 28 | .IsConcurrencyToken(); 29 | 30 | b.Property("Name") 31 | .HasMaxLength(256); 32 | 33 | b.Property("NormalizedName") 34 | .HasMaxLength(256); 35 | 36 | b.Property("UserId"); 37 | 38 | b.HasKey("Id"); 39 | 40 | b.HasIndex("NormalizedName") 41 | .IsUnique() 42 | .HasName("RoleNameIndex"); 43 | 44 | b.HasIndex("UserId"); 45 | 46 | b.ToTable("AspNetRoles"); 47 | }); 48 | 49 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 50 | { 51 | b.Property("Id") 52 | .ValueGeneratedOnAdd(); 53 | 54 | b.Property("ClaimType"); 55 | 56 | b.Property("ClaimValue"); 57 | 58 | b.Property("RoleId") 59 | .IsRequired(); 60 | 61 | b.HasKey("Id"); 62 | 63 | b.HasIndex("RoleId"); 64 | 65 | b.ToTable("AspNetRoleClaims"); 66 | }); 67 | 68 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 69 | { 70 | b.Property("Id") 71 | .ValueGeneratedOnAdd(); 72 | 73 | b.Property("ClaimType"); 74 | 75 | b.Property("ClaimValue"); 76 | 77 | b.Property("UserId") 78 | .IsRequired(); 79 | 80 | b.HasKey("Id"); 81 | 82 | b.HasIndex("UserId"); 83 | 84 | b.ToTable("AspNetUserClaims"); 85 | }); 86 | 87 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 88 | { 89 | b.Property("LoginProvider"); 90 | 91 | b.Property("ProviderKey"); 92 | 93 | b.Property("ProviderDisplayName"); 94 | 95 | b.Property("UserId") 96 | .IsRequired(); 97 | 98 | b.HasKey("LoginProvider", "ProviderKey"); 99 | 100 | b.HasIndex("UserId"); 101 | 102 | b.ToTable("AspNetUserLogins"); 103 | }); 104 | 105 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 106 | { 107 | b.Property("UserId"); 108 | 109 | b.Property("RoleId"); 110 | 111 | b.HasKey("UserId", "RoleId"); 112 | 113 | b.HasIndex("RoleId"); 114 | 115 | b.ToTable("AspNetUserRoles"); 116 | }); 117 | 118 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 119 | { 120 | b.Property("UserId"); 121 | 122 | b.Property("LoginProvider"); 123 | 124 | b.Property("Name"); 125 | 126 | b.Property("Value"); 127 | 128 | b.HasKey("UserId", "LoginProvider", "Name"); 129 | 130 | b.ToTable("AspNetUserTokens"); 131 | }); 132 | 133 | modelBuilder.Entity("NetCoreBootstrap.Models.Database.User", b => 134 | { 135 | b.Property("Id") 136 | .ValueGeneratedOnAdd(); 137 | 138 | b.Property("AccessFailedCount"); 139 | 140 | b.Property("ConcurrencyStamp") 141 | .IsConcurrencyToken(); 142 | 143 | b.Property("Email") 144 | .HasMaxLength(256); 145 | 146 | b.Property("EmailConfirmed"); 147 | 148 | b.Property("IsExternal"); 149 | 150 | b.Property("LockoutEnabled"); 151 | 152 | b.Property("LockoutEnd"); 153 | 154 | b.Property("NormalizedEmail") 155 | .HasMaxLength(256); 156 | 157 | b.Property("NormalizedUserName") 158 | .HasMaxLength(256); 159 | 160 | b.Property("PasswordHash"); 161 | 162 | b.Property("PhoneNumber"); 163 | 164 | b.Property("PhoneNumberConfirmed"); 165 | 166 | b.Property("SecurityStamp"); 167 | 168 | b.Property("TwoFactorEnabled"); 169 | 170 | b.Property("UserName") 171 | .HasMaxLength(256); 172 | 173 | b.HasKey("Id"); 174 | 175 | b.HasIndex("NormalizedEmail") 176 | .HasName("EmailIndex"); 177 | 178 | b.HasIndex("NormalizedUserName") 179 | .IsUnique() 180 | .HasName("UserNameIndex"); 181 | 182 | b.ToTable("AspNetUsers"); 183 | }); 184 | 185 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 186 | { 187 | b.HasOne("NetCoreBootstrap.Models.Database.User") 188 | .WithMany("Roles") 189 | .HasForeignKey("UserId"); 190 | }); 191 | 192 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 193 | { 194 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 195 | .WithMany() 196 | .HasForeignKey("RoleId") 197 | .OnDelete(DeleteBehavior.Cascade); 198 | }); 199 | 200 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 201 | { 202 | b.HasOne("NetCoreBootstrap.Models.Database.User") 203 | .WithMany() 204 | .HasForeignKey("UserId") 205 | .OnDelete(DeleteBehavior.Cascade); 206 | }); 207 | 208 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 209 | { 210 | b.HasOne("NetCoreBootstrap.Models.Database.User") 211 | .WithMany() 212 | .HasForeignKey("UserId") 213 | .OnDelete(DeleteBehavior.Cascade); 214 | }); 215 | 216 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 217 | { 218 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 219 | .WithMany() 220 | .HasForeignKey("RoleId") 221 | .OnDelete(DeleteBehavior.Cascade); 222 | 223 | b.HasOne("NetCoreBootstrap.Models.Database.User") 224 | .WithMany() 225 | .HasForeignKey("UserId") 226 | .OnDelete(DeleteBehavior.Cascade); 227 | }); 228 | 229 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 230 | { 231 | b.HasOne("NetCoreBootstrap.Models.Database.User") 232 | .WithMany() 233 | .HasForeignKey("UserId") 234 | .OnDelete(DeleteBehavior.Cascade); 235 | }); 236 | #pragma warning restore 612, 618 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/Migrations/20180226200614_InitialMigration.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 5 | 6 | namespace NetCoreBootstrap.Migrations 7 | { 8 | public partial class InitialMigration : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "AspNetUsers", 14 | columns: table => new 15 | { 16 | Id = table.Column(nullable: false), 17 | AccessFailedCount = table.Column(nullable: false), 18 | ConcurrencyStamp = table.Column(nullable: true), 19 | Email = table.Column(maxLength: 256, nullable: true), 20 | EmailConfirmed = table.Column(nullable: false), 21 | IsExternal = table.Column(nullable: false), 22 | LockoutEnabled = table.Column(nullable: false), 23 | LockoutEnd = table.Column(nullable: true), 24 | NormalizedEmail = table.Column(maxLength: 256, nullable: true), 25 | NormalizedUserName = table.Column(maxLength: 256, nullable: true), 26 | PasswordHash = table.Column(nullable: true), 27 | PhoneNumber = table.Column(nullable: true), 28 | PhoneNumberConfirmed = table.Column(nullable: false), 29 | SecurityStamp = table.Column(nullable: true), 30 | TwoFactorEnabled = table.Column(nullable: false), 31 | UserName = table.Column(maxLength: 256, nullable: true) 32 | }, 33 | constraints: table => 34 | { 35 | table.PrimaryKey("PK_AspNetUsers", x => x.Id); 36 | }); 37 | 38 | migrationBuilder.CreateTable( 39 | name: "AspNetRoles", 40 | columns: table => new 41 | { 42 | Id = table.Column(nullable: false), 43 | ConcurrencyStamp = table.Column(nullable: true), 44 | Name = table.Column(maxLength: 256, nullable: true), 45 | NormalizedName = table.Column(maxLength: 256, nullable: true), 46 | UserId = table.Column(nullable: true) 47 | }, 48 | constraints: table => 49 | { 50 | table.PrimaryKey("PK_AspNetRoles", x => x.Id); 51 | table.ForeignKey( 52 | name: "FK_AspNetRoles_AspNetUsers_UserId", 53 | column: x => x.UserId, 54 | principalTable: "AspNetUsers", 55 | principalColumn: "Id", 56 | onDelete: ReferentialAction.Restrict); 57 | }); 58 | 59 | migrationBuilder.CreateTable( 60 | name: "AspNetUserClaims", 61 | columns: table => new 62 | { 63 | Id = table.Column(nullable: false) 64 | .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn), 65 | ClaimType = table.Column(nullable: true), 66 | ClaimValue = table.Column(nullable: true), 67 | UserId = table.Column(nullable: false) 68 | }, 69 | constraints: table => 70 | { 71 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 72 | table.ForeignKey( 73 | name: "FK_AspNetUserClaims_AspNetUsers_UserId", 74 | column: x => x.UserId, 75 | principalTable: "AspNetUsers", 76 | principalColumn: "Id", 77 | onDelete: ReferentialAction.Cascade); 78 | }); 79 | 80 | migrationBuilder.CreateTable( 81 | name: "AspNetUserLogins", 82 | columns: table => new 83 | { 84 | LoginProvider = table.Column(nullable: false), 85 | ProviderKey = table.Column(nullable: false), 86 | ProviderDisplayName = table.Column(nullable: true), 87 | UserId = table.Column(nullable: false) 88 | }, 89 | constraints: table => 90 | { 91 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 92 | table.ForeignKey( 93 | name: "FK_AspNetUserLogins_AspNetUsers_UserId", 94 | column: x => x.UserId, 95 | principalTable: "AspNetUsers", 96 | principalColumn: "Id", 97 | onDelete: ReferentialAction.Cascade); 98 | }); 99 | 100 | migrationBuilder.CreateTable( 101 | name: "AspNetUserTokens", 102 | columns: table => new 103 | { 104 | UserId = table.Column(nullable: false), 105 | LoginProvider = table.Column(nullable: false), 106 | Name = table.Column(nullable: false), 107 | Value = table.Column(nullable: true) 108 | }, 109 | constraints: table => 110 | { 111 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 112 | table.ForeignKey( 113 | name: "FK_AspNetUserTokens_AspNetUsers_UserId", 114 | column: x => x.UserId, 115 | principalTable: "AspNetUsers", 116 | principalColumn: "Id", 117 | onDelete: ReferentialAction.Cascade); 118 | }); 119 | 120 | migrationBuilder.CreateTable( 121 | name: "AspNetRoleClaims", 122 | columns: table => new 123 | { 124 | Id = table.Column(nullable: false) 125 | .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn), 126 | ClaimType = table.Column(nullable: true), 127 | ClaimValue = table.Column(nullable: true), 128 | RoleId = table.Column(nullable: false) 129 | }, 130 | constraints: table => 131 | { 132 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 133 | table.ForeignKey( 134 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", 135 | column: x => x.RoleId, 136 | principalTable: "AspNetRoles", 137 | principalColumn: "Id", 138 | onDelete: ReferentialAction.Cascade); 139 | }); 140 | 141 | migrationBuilder.CreateTable( 142 | name: "AspNetUserRoles", 143 | columns: table => new 144 | { 145 | UserId = table.Column(nullable: false), 146 | RoleId = table.Column(nullable: false) 147 | }, 148 | constraints: table => 149 | { 150 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 151 | table.ForeignKey( 152 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId", 153 | column: x => x.RoleId, 154 | principalTable: "AspNetRoles", 155 | principalColumn: "Id", 156 | onDelete: ReferentialAction.Cascade); 157 | table.ForeignKey( 158 | name: "FK_AspNetUserRoles_AspNetUsers_UserId", 159 | column: x => x.UserId, 160 | principalTable: "AspNetUsers", 161 | principalColumn: "Id", 162 | onDelete: ReferentialAction.Cascade); 163 | }); 164 | 165 | migrationBuilder.CreateIndex( 166 | name: "IX_AspNetRoleClaims_RoleId", 167 | table: "AspNetRoleClaims", 168 | column: "RoleId"); 169 | 170 | migrationBuilder.CreateIndex( 171 | name: "RoleNameIndex", 172 | table: "AspNetRoles", 173 | column: "NormalizedName", 174 | unique: true); 175 | 176 | migrationBuilder.CreateIndex( 177 | name: "IX_AspNetRoles_UserId", 178 | table: "AspNetRoles", 179 | column: "UserId"); 180 | 181 | migrationBuilder.CreateIndex( 182 | name: "IX_AspNetUserClaims_UserId", 183 | table: "AspNetUserClaims", 184 | column: "UserId"); 185 | 186 | migrationBuilder.CreateIndex( 187 | name: "IX_AspNetUserLogins_UserId", 188 | table: "AspNetUserLogins", 189 | column: "UserId"); 190 | 191 | migrationBuilder.CreateIndex( 192 | name: "IX_AspNetUserRoles_RoleId", 193 | table: "AspNetUserRoles", 194 | column: "RoleId"); 195 | 196 | migrationBuilder.CreateIndex( 197 | name: "EmailIndex", 198 | table: "AspNetUsers", 199 | column: "NormalizedEmail"); 200 | 201 | migrationBuilder.CreateIndex( 202 | name: "UserNameIndex", 203 | table: "AspNetUsers", 204 | column: "NormalizedUserName", 205 | unique: true); 206 | } 207 | 208 | protected override void Down(MigrationBuilder migrationBuilder) 209 | { 210 | migrationBuilder.DropTable( 211 | name: "AspNetRoleClaims"); 212 | 213 | migrationBuilder.DropTable( 214 | name: "AspNetUserClaims"); 215 | 216 | migrationBuilder.DropTable( 217 | name: "AspNetUserLogins"); 218 | 219 | migrationBuilder.DropTable( 220 | name: "AspNetUserRoles"); 221 | 222 | migrationBuilder.DropTable( 223 | name: "AspNetUserTokens"); 224 | 225 | migrationBuilder.DropTable( 226 | name: "AspNetRoles"); 227 | 228 | migrationBuilder.DropTable( 229 | name: "AspNetUsers"); 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/Migrations/DataBaseContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using NetCoreBootstrap.Repositories.Database; 6 | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 7 | 8 | namespace NetCoreBootstrap.Migrations 9 | { 10 | [DbContext(typeof(DataBaseContext))] 11 | partial class DataBaseContextModelSnapshot : ModelSnapshot 12 | { 13 | protected override void BuildModel(ModelBuilder modelBuilder) 14 | { 15 | #pragma warning disable 612, 618 16 | modelBuilder 17 | .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) 18 | .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); 19 | 20 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd(); 24 | 25 | b.Property("ConcurrencyStamp") 26 | .IsConcurrencyToken(); 27 | 28 | b.Property("Name") 29 | .HasMaxLength(256); 30 | 31 | b.Property("NormalizedName") 32 | .HasMaxLength(256); 33 | 34 | b.Property("UserId"); 35 | 36 | b.HasKey("Id"); 37 | 38 | b.HasIndex("NormalizedName") 39 | .IsUnique() 40 | .HasName("RoleNameIndex"); 41 | 42 | b.HasIndex("UserId"); 43 | 44 | b.ToTable("AspNetRoles"); 45 | }); 46 | 47 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 48 | { 49 | b.Property("Id") 50 | .ValueGeneratedOnAdd(); 51 | 52 | b.Property("ClaimType"); 53 | 54 | b.Property("ClaimValue"); 55 | 56 | b.Property("RoleId") 57 | .IsRequired(); 58 | 59 | b.HasKey("Id"); 60 | 61 | b.HasIndex("RoleId"); 62 | 63 | b.ToTable("AspNetRoleClaims"); 64 | }); 65 | 66 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 67 | { 68 | b.Property("Id") 69 | .ValueGeneratedOnAdd(); 70 | 71 | b.Property("ClaimType"); 72 | 73 | b.Property("ClaimValue"); 74 | 75 | b.Property("UserId") 76 | .IsRequired(); 77 | 78 | b.HasKey("Id"); 79 | 80 | b.HasIndex("UserId"); 81 | 82 | b.ToTable("AspNetUserClaims"); 83 | }); 84 | 85 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 86 | { 87 | b.Property("LoginProvider"); 88 | 89 | b.Property("ProviderKey"); 90 | 91 | b.Property("ProviderDisplayName"); 92 | 93 | b.Property("UserId") 94 | .IsRequired(); 95 | 96 | b.HasKey("LoginProvider", "ProviderKey"); 97 | 98 | b.HasIndex("UserId"); 99 | 100 | b.ToTable("AspNetUserLogins"); 101 | }); 102 | 103 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 104 | { 105 | b.Property("UserId"); 106 | 107 | b.Property("RoleId"); 108 | 109 | b.HasKey("UserId", "RoleId"); 110 | 111 | b.HasIndex("RoleId"); 112 | 113 | b.ToTable("AspNetUserRoles"); 114 | }); 115 | 116 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 117 | { 118 | b.Property("UserId"); 119 | 120 | b.Property("LoginProvider"); 121 | 122 | b.Property("Name"); 123 | 124 | b.Property("Value"); 125 | 126 | b.HasKey("UserId", "LoginProvider", "Name"); 127 | 128 | b.ToTable("AspNetUserTokens"); 129 | }); 130 | 131 | modelBuilder.Entity("NetCoreBootstrap.Models.Database.User", b => 132 | { 133 | b.Property("Id") 134 | .ValueGeneratedOnAdd(); 135 | 136 | b.Property("AccessFailedCount"); 137 | 138 | b.Property("ConcurrencyStamp") 139 | .IsConcurrencyToken(); 140 | 141 | b.Property("Email") 142 | .HasMaxLength(256); 143 | 144 | b.Property("EmailConfirmed"); 145 | 146 | b.Property("IsExternal"); 147 | 148 | b.Property("LockoutEnabled"); 149 | 150 | b.Property("LockoutEnd"); 151 | 152 | b.Property("NormalizedEmail") 153 | .HasMaxLength(256); 154 | 155 | b.Property("NormalizedUserName") 156 | .HasMaxLength(256); 157 | 158 | b.Property("PasswordHash"); 159 | 160 | b.Property("PhoneNumber"); 161 | 162 | b.Property("PhoneNumberConfirmed"); 163 | 164 | b.Property("SecurityStamp"); 165 | 166 | b.Property("TwoFactorEnabled"); 167 | 168 | b.Property("UserName") 169 | .HasMaxLength(256); 170 | 171 | b.HasKey("Id"); 172 | 173 | b.HasIndex("NormalizedEmail") 174 | .HasName("EmailIndex"); 175 | 176 | b.HasIndex("NormalizedUserName") 177 | .IsUnique() 178 | .HasName("UserNameIndex"); 179 | 180 | b.ToTable("AspNetUsers"); 181 | }); 182 | 183 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 184 | { 185 | b.HasOne("NetCoreBootstrap.Models.Database.User") 186 | .WithMany("Roles") 187 | .HasForeignKey("UserId"); 188 | }); 189 | 190 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 191 | { 192 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 193 | .WithMany() 194 | .HasForeignKey("RoleId") 195 | .OnDelete(DeleteBehavior.Cascade); 196 | }); 197 | 198 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 199 | { 200 | b.HasOne("NetCoreBootstrap.Models.Database.User") 201 | .WithMany() 202 | .HasForeignKey("UserId") 203 | .OnDelete(DeleteBehavior.Cascade); 204 | }); 205 | 206 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 207 | { 208 | b.HasOne("NetCoreBootstrap.Models.Database.User") 209 | .WithMany() 210 | .HasForeignKey("UserId") 211 | .OnDelete(DeleteBehavior.Cascade); 212 | }); 213 | 214 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 215 | { 216 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 217 | .WithMany() 218 | .HasForeignKey("RoleId") 219 | .OnDelete(DeleteBehavior.Cascade); 220 | 221 | b.HasOne("NetCoreBootstrap.Models.Database.User") 222 | .WithMany() 223 | .HasForeignKey("UserId") 224 | .OnDelete(DeleteBehavior.Cascade); 225 | }); 226 | 227 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 228 | { 229 | b.HasOne("NetCoreBootstrap.Models.Database.User") 230 | .WithMany() 231 | .HasForeignKey("UserId") 232 | .OnDelete(DeleteBehavior.Cascade); 233 | }); 234 | #pragma warning restore 612, 618 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/Models/Database/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace NetCoreBootstrap.Models.Database 5 | { 6 | public class BaseEntity 7 | { 8 | public int Id { get; set; } 9 | public DateTime CreatedAt { get; set; } 10 | public DateTime UpdatedAt { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Models/Database/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | 8 | namespace NetCoreBootstrap.Models.Database 9 | { 10 | public class User : IdentityUser 11 | { 12 | public bool IsExternal { get; set; } 13 | public virtual ICollection Roles { get; set; } 14 | 15 | public bool IsEmailValid() => 16 | new Regex("(@[a-zA-Z]{1,})(\\.[a-zA-Z]{2,}){1,2}").Match(Email).Success; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Models/VOs/UserVO.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace NetCoreBootstrap.Models.VOs 4 | { 5 | public class UserVO 6 | { 7 | public string UserName { get; set; } 8 | public string Email { get; set; } 9 | public string FirstName { get; set; } 10 | public string LastName { get; set; } 11 | public string Token { get; set; } 12 | 13 | [DataType(DataType.Password)] 14 | public string Password { get; set; } 15 | 16 | [DataType(DataType.Password)] 17 | public string ConfirmPassword { get; set; } 18 | 19 | public string NewPassword { get; set; } 20 | 21 | [Display(Name = "Remember me")] 22 | public bool RememberMe { get; set; } 23 | public string UserId { get; set; } 24 | public bool IsFacebook { get; set; } 25 | public bool IsGoogle { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Models/Views/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using Microsoft.AspNetCore.Authentication; 4 | 5 | namespace NetCoreBootstrap.Models.Views 6 | { 7 | public class LoginViewModel 8 | { 9 | [Required] 10 | public string UserName { get; set; } 11 | 12 | [Required] 13 | [DataType(DataType.Password)] 14 | public string Password { get; set; } 15 | 16 | [Display(Name = "Remember me")] 17 | public bool RememberMe { get; set; } 18 | 19 | public ICollection LoginProviders { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Models/Views/RoleManagerViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 3 | using Microsoft.AspNetCore.Mvc.Rendering; 4 | 5 | namespace NetCoreBootstrap.Models.Views 6 | { 7 | public class RoleManagerViewModel 8 | { 9 | public List UsersListItem { get; set; } 10 | public Dictionary Roles { get; set; } 11 | public string SelectedUserId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Models/Views/UserManagementViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | using NetCoreBootstrap.Models.Database; 7 | 8 | namespace NetCoreBootstrap.Models.Views 9 | { 10 | public class UserManagementViewModel 11 | { 12 | [MinLength(4)] 13 | public string Name { get; set; } 14 | public string UserId { get; set; } 15 | public string RoleId { get; set; } 16 | public string Email { get; set; } 17 | public string NewRole { get; set; } 18 | public List RolesListItem { get; set; } 19 | public List Users { get; set; } 20 | public List Roles { get; set; } 21 | 22 | [Display(Name = "Current Password")] 23 | [DataType(DataType.Password)] 24 | public string Password { get; set; } 25 | 26 | [MinLength(6)] 27 | [MaxLength(40)] 28 | [DataType(DataType.Password)] 29 | [Display(Name = "New password")] 30 | public string NewPassword { get; set; } 31 | 32 | [MinLength(6)] 33 | [MaxLength(40)] 34 | [DataType(DataType.Password)] 35 | [Display(Name = "Confirm new password")] 36 | [Compare("NewPassword", ErrorMessage = "The password doesn't match the confirmation password")] 37 | public string ConfirmNewPassword { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Models/Views/UserViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace NetCoreBootstrap.Models.Views 4 | { 5 | public class UserViewModel 6 | { 7 | [Required] 8 | public string UserName { get; set; } 9 | 10 | [Required] 11 | [EmailAddress] 12 | [MaxLength(256)] 13 | [Display(Name = "Email Address")] 14 | public string Email { get; set; } 15 | 16 | [Required] 17 | [MinLength(6)] 18 | [MaxLength(40)] 19 | [DataType(DataType.Password)] 20 | [Display(Name = "Password")] 21 | public string Password { get; set; } 22 | 23 | [Required] 24 | [MinLength(6)] 25 | [MaxLength(40)] 26 | [DataType(DataType.Password)] 27 | [Display(Name = "Confirm Password")] 28 | [Compare("Password", ErrorMessage = "The password doesn't match the confirmation password")] 29 | public string ConfirmPassword { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/NetCoreBootstrap.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | ./ca.ruleset 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace NetCoreBootstrap 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Repositories/Database/DataBaseContext.cs: -------------------------------------------------------------------------------- 1 | #region Using 2 | using System; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore; 7 | using NetCoreBootstrap.Models.Database; 8 | 9 | #endregion 10 | 11 | namespace NetCoreBootstrap.Repositories.Database 12 | { 13 | public class DataBaseContext : IdentityDbContext 14 | { 15 | public DataBaseContext(DbContextOptions options) : base(options) { } 16 | public override int SaveChanges() 17 | { 18 | AddTimestamps(); 19 | return base.SaveChanges(); 20 | } 21 | 22 | protected override void OnModelCreating(ModelBuilder modelBuilder) 23 | { 24 | base.OnModelCreating(modelBuilder); 25 | } 26 | 27 | private void AddTimestamps() 28 | { 29 | var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && (x.State == EntityState.Added || x.State == EntityState.Modified)); 30 | foreach (var entity in entities) 31 | { 32 | if (entity.State == EntityState.Added) 33 | { 34 | ((BaseEntity)entity.Entity).CreatedAt = DateTime.UtcNow; 35 | } 36 | ((BaseEntity)entity.Entity).UpdatedAt = DateTime.UtcNow; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Repositories/Database/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using NetCoreBootstrap.Models.Database; 3 | using NetCoreBootstrap.Repositories.Interfaces; 4 | 5 | namespace NetCoreBootstrap.Repositories.Database 6 | { 7 | public class UnitOfWork : IUnitOfWork 8 | { 9 | private readonly DataBaseContext _context; 10 | 11 | public UnitOfWork(DataBaseContext context, UserManager userManager, RoleManager roleManager) 12 | { 13 | this._context = context; 14 | this.UserRepository = new UserRepository(context, userManager, roleManager); 15 | } 16 | 17 | public IUserRepository UserRepository { get; private set; } 18 | 19 | public int Complete() 20 | { 21 | return this._context.SaveChanges(); 22 | } 23 | 24 | public void Dispose() 25 | { 26 | this._context.Dispose(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Repositories/Interfaces/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace NetCoreBootstrap.Repositories.Interfaces 6 | { 7 | public interface IRepository where T : class 8 | { 9 | void Add(T entity); 10 | void AddRange(IEnumerable entities); 11 | void Remove(T entity); 12 | void RemoveRange(IEnumerable entities); 13 | bool Update(T entity); 14 | void UpdateRange(IEnumerable entities); 15 | T Get(int id); 16 | IEnumerable GetAll(); 17 | IEnumerable Find(Expression> predicate); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Repositories/Interfaces/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetCoreBootstrap.Repositories.Interfaces 4 | { 5 | public interface IUnitOfWork : IDisposable 6 | { 7 | IUserRepository UserRepository { get; } 8 | int Complete(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Repositories/Interfaces/IUserRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc.Rendering; 5 | using NetCoreBootstrap.Models.Database; 6 | 7 | namespace NetCoreBootstrap.Repositories.Interfaces 8 | { 9 | public interface IUserRepository 10 | { 11 | Task GetUserById(string id); 12 | Task IsUserInRole(User user, string role); 13 | List GetAllUsers(); 14 | List GetAllUsersWithRoles(); 15 | List GetAllRoles(); 16 | Task AddRoleToUser(User user, string role); 17 | Dictionary GetRoleMap(); 18 | Task RemoveRoleFromUser(User user, string role); 19 | Task CreateRole(string role); 20 | Task DeleteRole(string roleId); 21 | Task UpdateRole(string roleId, string name); 22 | Task GetRoleById(string roleId); 23 | Task> GetRoles(User user); 24 | List GetUsersListItem(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Repositories/Repository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Microsoft.EntityFrameworkCore; 6 | using NetCoreBootstrap.Repositories.Interfaces; 7 | 8 | namespace NetCoreBootstrap.Repositories 9 | { 10 | public class Repository : IRepository where T : class 11 | { 12 | private readonly DbContext _context; 13 | 14 | public Repository(DbContext context) => _context = context; 15 | 16 | public void Add(T entity) => _context.Set().Add(entity); 17 | 18 | public void AddRange(IEnumerable entities) => _context.Set().AddRange(entities); 19 | 20 | public T Get(int id) => _context.Set().Find(id); 21 | 22 | public IEnumerable GetAll() => _context.Set().ToList(); 23 | 24 | public void Remove(T entity) => _context.Set().Remove(entity); 25 | 26 | public void RemoveRange(IEnumerable entities) => _context.Set().RemoveRange(entities); 27 | 28 | public IEnumerable Find(Expression> predicate) => _context.Set().Where(predicate); 29 | 30 | public void UpdateRange(IEnumerable entities) => _context.Set().UpdateRange(entities); 31 | 32 | public bool Update(T entity) 33 | { 34 | var updateResult = _context.Set().Update(entity); 35 | return updateResult.State == EntityState.Modified; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Repositories/UserRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 7 | using Microsoft.AspNetCore.Mvc.Rendering; 8 | using Microsoft.EntityFrameworkCore; 9 | using NetCoreBootstrap.Models.Database; 10 | using NetCoreBootstrap.Repositories.Database; 11 | using NetCoreBootstrap.Repositories.Interfaces; 12 | 13 | namespace NetCoreBootstrap.Repositories 14 | { 15 | public class UserRepository : IUserRepository 16 | { 17 | private readonly DataBaseContext _context; 18 | private readonly UserManager _userManager; 19 | private readonly RoleManager _roleManager; 20 | 21 | public UserRepository(DataBaseContext context, UserManager userManager, RoleManager roleManager) 22 | { 23 | this._context = context; 24 | this._userManager = userManager; 25 | this._roleManager = roleManager; 26 | } 27 | 28 | public UserManager UserManager { get => this._userManager; } 29 | 30 | public RoleManager RoleManager { get => this._roleManager; } 31 | 32 | public DataBaseContext Context { get => this._context; } 33 | 34 | public async Task GetUserById(string id) 35 | { 36 | return await UserManager.FindByIdAsync(id); 37 | } 38 | 39 | public async Task IsUserInRole(User user, string role) 40 | { 41 | return await UserManager.IsInRoleAsync(user, role); 42 | } 43 | 44 | public List GetAllUsers() 45 | { 46 | return UserManager.Users.ToList(); 47 | } 48 | 49 | public List GetAllUsersWithRoles() 50 | { 51 | using (var context = Context) 52 | { 53 | return (from user in context.Users 54 | select new User 55 | { 56 | Id = user.Id, 57 | Email = user.Email, 58 | UserName = user.UserName, 59 | Roles = (from role in context.Roles 60 | join userRole in context.UserRoles on role.Id equals userRole.RoleId 61 | where userRole.UserId == user.Id 62 | select role).ToList(), 63 | }).Distinct().ToList(); 64 | } 65 | } 66 | 67 | public List GetAllRoles() 68 | { 69 | return RoleManager.Roles.ToList(); 70 | } 71 | 72 | public async Task AddRoleToUser(User user, string role) 73 | { 74 | return await UserManager.AddToRoleAsync(user, role); 75 | } 76 | 77 | public Dictionary GetRoleMap() 78 | { 79 | var map = new Dictionary(); 80 | foreach (var role in GetAllRoles()) 81 | { 82 | map[role.Id] = role.Name; 83 | } 84 | return map; 85 | } 86 | 87 | public async Task RemoveRoleFromUser(User user, string role) 88 | { 89 | return await UserManager.RemoveFromRoleAsync(user, role); 90 | } 91 | 92 | public async Task CreateRole(string role) 93 | { 94 | return await RoleManager.CreateAsync(new IdentityRole(role)); 95 | } 96 | 97 | public async Task DeleteRole(string roleId) 98 | { 99 | var role = await RoleManager.FindByIdAsync(roleId); 100 | try 101 | { 102 | return (await RoleManager.DeleteAsync(role)).Succeeded; 103 | } 104 | catch (Exception) 105 | { 106 | return false; 107 | } 108 | } 109 | 110 | public async Task UpdateRole(string roleId, string name) 111 | { 112 | try 113 | { 114 | var role = await GetRoleById(roleId); 115 | role.Name = name; 116 | return (await RoleManager.UpdateAsync(role)).Succeeded; 117 | } 118 | catch (Exception) 119 | { 120 | return false; 121 | } 122 | } 123 | 124 | public async Task GetRoleById(string roleId) 125 | { 126 | try 127 | { 128 | return await RoleManager.FindByIdAsync(roleId); 129 | } 130 | catch (Exception e) 131 | { 132 | throw new Exception(e.Message); 133 | } 134 | } 135 | 136 | public async Task> GetRoles(User user) 137 | { 138 | return await UserManager.GetRolesAsync(user); 139 | } 140 | 141 | public List GetUsersListItem() 142 | { 143 | return (from user in UserManager.Users.OrderBy(u => u.Email) select new SelectListItem { Text = user.Email, Value = user.Id }).ToList(); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Resources/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "DescriptionPage": "Your application description page.", 3 | "ContactPage": "Your contact page.", 4 | "HomePage": "Home Page", 5 | "HowToBuild": "Learn how to build ASP.NET apps that can run anywhere.", 6 | "LearnMore": "Learn More", 7 | "FeaturesInVSForModernWebApps": "There are powerful new features in Visual Studio for building modern web apps.", 8 | "BringInLibraries": "Bring in libraries from NuGet, Bower, and npm, and automate tasks using Grunt or Gulp.", 9 | "AzureDeploy": "Learn how Microsoft's Azure cloud platform allows you to build, deploy, and scale web apps.", 10 | "Previous": "Previous", 11 | "Next": "Next", 12 | "About": "About", 13 | "Contact": "Contact", 14 | "SaveChanges": "Save changes", 15 | "NewRole": "New role", 16 | "Roles": "Roles", 17 | "Users": "Users", 18 | "RoleManager": "Role manager", 19 | "EditRole": "Edit role", 20 | "DeleteRole": "Delete role", 21 | "CreateRole": "Create role", 22 | "Hello": "Hello", 23 | "Register": "Register", 24 | "Login": "Log in", 25 | "Logout": "Log out", 26 | "Profile": "Profile", 27 | "ChangePassword": "Change password", 28 | "AccessDenied": "Access denied", 29 | "AccessDeniedDescription": "You do not have access to this resource.", 30 | "RoleDeleted": "The role was successfully deleted.", 31 | "RoleNotDeleted": "The role could not be deleted.", 32 | "RoleUpdated": "The role was successfully updated.", 33 | "RoleNotUpdated": "The role could not be updated." 34 | } 35 | -------------------------------------------------------------------------------- /src/Resources/es-AR.json: -------------------------------------------------------------------------------- 1 | { 2 | "DescriptionPage": "Página de descripción de la aplicación", 3 | "ContactPage": "Página de contacto", 4 | "HomePage": "Página de inicio", 5 | "HowToBuild": "Aprende a crear aplicaciones ASP.NET que se pueden ejecutar en cualquier lugar.", 6 | "LearnMore": "Leer más", 7 | "FeaturesInVSForModernWebApps": "Hay nuevas y potentes funciones en Visual Studio para crear aplicaciones web modernas.", 8 | "BringInLibraries": "Descargue librerías con NuGet, Bower y npm, y automatice las tareas usando Grunt o Gulp.", 9 | "AzureDeploy": "Descubra cómo la plataforma de cloud de Azure de Microsoft le permite crear, implementar y escalar aplicaciones web.", 10 | "Previous": "Anterior", 11 | "Next": "Siguiente", 12 | "About": "Acerca de", 13 | "Contact": "Contacto", 14 | "SaveChanges": "Guardar cambios", 15 | "NewRole": "Nuevo rol", 16 | "Roles": "Roles", 17 | "Users": "Usuarios", 18 | "RoleManager": "Administrador de roles", 19 | "EditRole": "Editar rol", 20 | "DeleteRole": "Borrar rol", 21 | "CreateRole": "Crear rol", 22 | "Hello": "Hola", 23 | "Register": "Registro", 24 | "Login": "Iniciar sesión", 25 | "Logout": "Cerrar sesión", 26 | "Profile": "Perfil", 27 | "ChangePassword": "Cambiar contraseña", 28 | "AccessDenied": "Acceso denegado", 29 | "AccessDeniedDescription": "No tiene acceso a este recurso.", 30 | "RoleDeleted": "El rol se eliminó correctamente.", 31 | "RoleNotDeleted": "El rol no pudo ser eliminado.", 32 | "RoleUpdated": "El rol se actualizó correctamente.", 33 | "RoleNotUpdated": "El rol no pudo ser actualizado." 34 | } 35 | -------------------------------------------------------------------------------- /src/Scripts/Src/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace BootstrapScript 7 | { 8 | public class Program 9 | { 10 | public static int RemoveThisMain(string[] args) 11 | { 12 | string appName = args[0]; 13 | string deleteAuth = string.Empty; 14 | if (args.Length > 1) deleteAuth = args[1]; 15 | if (!string.IsNullOrEmpty(deleteAuth) && deleteAuth != DeleteAuthenticationParamValue()) 16 | { 17 | Console.ForegroundColor = ConsoleColor.Yellow; 18 | Console.WriteLine($@"Params not found: {deleteAuth}"); 19 | Console.ResetColor(); 20 | } 21 | Console.WriteLine("Setting up bootstrap for " + appName); 22 | string bootstrapRootDir = string.Empty; 23 | string bootstrapName = "NetCoreBootstrap"; 24 | foreach (var dir in Directory.GetCurrentDirectory().Split('/')) 25 | { 26 | bootstrapRootDir += dir + "/"; 27 | if (dir == PascalToKebabCase(appName)) break; 28 | } 29 | Console.WriteLine("Replacing " + bootstrapName + " to " + appName + " in " + bootstrapRootDir); 30 | string contents = string.Empty; 31 | var files = from string file in Directory.EnumerateFiles(bootstrapRootDir, "*", SearchOption.AllDirectories) where !(file.Contains("bootstrap-script") || file.Contains("script.sh")) select file; 32 | string endOfLine = string.Empty; 33 | int startIndex, endIndex; 34 | foreach (string file in files) 35 | { 36 | contents = File.ReadAllText(file); 37 | if (contents.Contains("\r\n")) endOfLine = "\r\n"; 38 | else if (contents.Contains("\n")) endOfLine = "\n"; 39 | if (contents.Contains(bootstrapName)) 40 | { 41 | contents = contents.Replace(bootstrapName, appName); 42 | if (file.Contains("/README.md")) 43 | { 44 | contents = contents.Replace($@"### [Kickoff] Application Setup{endOfLine}{endOfLine}", string.Empty) 45 | .Replace($@"After cloning the bootstrap, follow the [kickoff guide](https://github.com/Wolox/tech-guides/blob/master/net-core/docs/kickoff/README.md#kickoff).{endOfLine}", string.Empty) 46 | .Replace($@"And happy coding!{endOfLine}", string.Empty); 47 | } 48 | if (deleteAuth == DeleteAuthenticationParamValue()) 49 | { 50 | if (file.Contains("/Startup.cs")) 51 | { 52 | Console.WriteLine("Updating Startup.cs ..."); 53 | // We use spaces to delete indentation 54 | contents = contents.Replace($@"using Microsoft.AspNetCore.Identity.EntityFrameworkCore;{endOfLine}", string.Empty) 55 | .Replace($@"using Microsoft.AspNetCore.Identity;{endOfLine}", string.Empty) 56 | .Replace($@"using Microsoft.AspNetCore.Authentication.Google;{endOfLine}", string.Empty) 57 | .Replace($@"app.UseAuthentication();{endOfLine} ", string.Empty); 58 | startIndex = contents.IndexOf("// Begin for Identity"); 59 | endIndex = contents.IndexOf("// Final for Identity", startIndex + 1); 60 | contents = contents.Remove(startIndex, endIndex - startIndex); 61 | contents = contents.Replace($"// Final for Identity;{endOfLine}", string.Empty); 62 | } 63 | else if (file.Contains("/Repositories/Database/DataBaseContext.cs")) 64 | { 65 | Console.WriteLine("Updating DataBaseContext.cs ..."); 66 | contents = contents.Replace("IdentityDbContext", "DbContext") 67 | .Replace($@"using Microsoft.AspNetCore.Identity.EntityFrameworkCore;{endOfLine}", string.Empty) 68 | .Replace($@"using Microsoft.AspNetCore.Identity;{endOfLine}", string.Empty); 69 | } 70 | else if (file.Contains("/Views/Shared/_Layout.cshtml")) 71 | { 72 | // We use spaces to delete indentation 73 | Console.WriteLine("Updating _Layout.cshtml ..."); 74 | contents = contents.Replace($" @await Html.PartialAsync(\"_UserManagementPartial\"){endOfLine} ", string.Empty) 75 | .Replace($" @await Html.PartialAsync(\"_LoginPartial\"){endOfLine} ", string.Empty); 76 | } 77 | } 78 | File.WriteAllText(file, contents); 79 | } 80 | else if (file.Contains($@"{bootstrapRootDir}{bootstrapName}.csproj") && deleteAuth == DeleteAuthenticationParamValue()) 81 | { 82 | Console.WriteLine("Updating csproj file ..."); 83 | // We use spaces to delete indentation 84 | contents = contents.Replace($"{endOfLine} ", string.Empty) 85 | .Replace($"{endOfLine} ", string.Empty); 86 | File.WriteAllText(file, contents); 87 | } 88 | } 89 | Console.WriteLine("Renaming .csproj ..."); 90 | File.Move($@"{bootstrapRootDir}/src/{bootstrapName}.csproj", $@"{bootstrapRootDir}/src/{appName}.csproj"); 91 | Console.WriteLine("Renaming Test .csproj ..."); 92 | File.Move($@"{bootstrapRootDir}/test/{bootstrapName}.Tests.csproj", $@"{bootstrapRootDir}/test/{appName}.Tests.csproj"); 93 | Console.WriteLine("Renaming sln ..."); 94 | File.Move($@"{bootstrapRootDir}/netcore-bootstrap.sln", $@"{bootstrapRootDir}/{PascalToKebabCase(appName)}.sln"); 95 | 96 | if (deleteAuth == DeleteAuthenticationParamValue()) 97 | { 98 | Console.WriteLine("Preparing to delete authentication files ..."); 99 | MoveAuthFilesToScriptFolder(bootstrapRootDir); 100 | } 101 | Console.WriteLine("Moving appsettings.Development.json ..."); 102 | File.Move($@"{bootstrapRootDir}/src/Scripts/appsettings.Development.json", $@"{bootstrapRootDir}/src/appsettings.Development.json"); 103 | Console.WriteLine(appName + " is ready! Happy coding!"); 104 | return 1; 105 | } 106 | 107 | private static void MoveAuthFilesToScriptFolder(string bootstrapRootDir) 108 | { 109 | Directory.Move($@"{bootstrapRootDir}/src/Migrations", $@"{bootstrapRootDir}/src/Scripts/Migrations"); 110 | Directory.Move($@"{bootstrapRootDir}/src/Views/Account", $@"{bootstrapRootDir}/src/Scripts/ViewsAccount"); 111 | Directory.Move($@"{bootstrapRootDir}/src/Views/UserManagement", $@"{bootstrapRootDir}/src/Scripts/ViewsUserManagement"); 112 | Directory.Move($@"{bootstrapRootDir}/src/Models/Views", $@"{bootstrapRootDir}/src/Scripts/ModelsViews"); 113 | File.Move($@"{bootstrapRootDir}/src/Controllers/AccountController.cs", $@"{bootstrapRootDir}/src/Scripts/ControllersAccountController.cs"); 114 | File.Move($@"{bootstrapRootDir}/src/Controllers/api/v1/AccountApiController.cs", $@"{bootstrapRootDir}/src/Scripts/Controllersapiv1AccountApiController.cs"); 115 | File.Move($@"{bootstrapRootDir}/src/Controllers/UserManagementController.cs", $@"{bootstrapRootDir}/src/Scripts/ControllersUserManagementController.cs"); 116 | File.Move($@"{bootstrapRootDir}/src/Models/Database/User.cs", $@"{bootstrapRootDir}/src/Scripts/ModelsDatabaseUser.cs"); 117 | File.Move($@"{bootstrapRootDir}/src/Repositories/UserRepository.cs", $@"{bootstrapRootDir}/src/Scripts/UserRepository.cs"); 118 | File.Move($@"{bootstrapRootDir}/src/Views/Shared/_LoginPartial.cshtml", $@"{bootstrapRootDir}/src/Scripts/_LoginPartial.cshtml"); 119 | File.Move($@"{bootstrapRootDir}/src/Views/Shared/_UserManagementPartial.cshtml", $@"{bootstrapRootDir}/src/Scripts/_UserManagementPartial.cshtml"); 120 | } 121 | 122 | private static string DeleteAuthenticationParamValue() 123 | { 124 | return "delete-auth"; 125 | } 126 | 127 | private static string PascalToKebabCase(string value) 128 | { 129 | if (string.IsNullOrEmpty(value)) 130 | return value; 131 | 132 | return Regex.Replace( 133 | value, 134 | "(? options.ResourcesPath = "Resources"); 47 | CultureInfo.CurrentUICulture = new CultureInfo(Configuration["DefaultCulture"]); 48 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddViewLocalization(); 49 | services.AddSwaggerGen(c => 50 | { 51 | c.SwaggerDoc("v1", new Info { Title = "NetCoreBootstrap API", Version = "v1" }); 52 | }); 53 | if (CurrentEnvironment.IsEnvironment("Testing")) 54 | { 55 | // If Testing environment, set in memory database 56 | var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" }; 57 | var connectionString = connectionStringBuilder.ToString(); 58 | var connection = new SqliteConnection(connectionString); 59 | services.AddDbContext(options => options.UseSqlite(connection)); 60 | } 61 | else 62 | { 63 | var connectionString = Configuration["ConnectionString"]; 64 | // if not, set the postgres database 65 | services.AddDbContext(options => options.UseNpgsql(connectionString)); 66 | } 67 | // Begin for Identity 68 | services.AddIdentity() 69 | .AddEntityFrameworkStores() 70 | .AddDefaultTokenProviders(); 71 | services.AddScoped(); 72 | services.ConfigureApplicationCookie(options => 73 | { 74 | options.LoginPath = "/Account/Login"; 75 | options.AccessDeniedPath = "/Account/AccessDenied"; 76 | }); 77 | services.Configure(options => 78 | { 79 | // This lambda determines whether user consent for non-essential cookies is needed for a given request. 80 | options.CheckConsentNeeded = context => true; 81 | options.MinimumSameSitePolicy = SameSiteMode.None; 82 | }); 83 | 84 | // ---------------------------------------------------------------------------------------- 85 | // JWT auth 86 | // To use this con the controllers, add the [Authorize] tag on the methods that require auth 87 | // services.AddAuthentication().AddJwtBearer(options => 88 | // { 89 | // options.Audience = Configuration["Jwt:Issuer"]; 90 | // options.TokenValidationParameters = new TokenValidationParameters() 91 | // { 92 | // ClockSkew = TimeSpan.FromMinutes(0), 93 | // ValidateLifetime = true, 94 | // ValidateIssuerSigningKey = true, 95 | // ValidAudience = Configuration["Jwt:Issuer"], 96 | // ValidIssuer = Configuration["Jwt:Issuer"], 97 | // IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Jwt:Key"])), 98 | // }; 99 | // }); 100 | // End JWT Auth 101 | // ---------------------------------------------------------------------------------------- 102 | // Facebook Auth 103 | // services.AddAuthentication().AddFacebook(facebookOptions => { 104 | // facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"]; 105 | // facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"]; 106 | // }); 107 | // End Facebook Auth 108 | // ---------------------------------------------------------------------------------------- 109 | // Google Auth 110 | // services.AddAuthentication().AddGoogle(googleOptions => 111 | // { 112 | // googleOptions.ClientId = Configuration["Authentication:GoogleAuth:ClientId"]; 113 | // googleOptions.ClientSecret = Configuration["Authentication:GoogleAuth:ClientSecret"]; 114 | // }); 115 | // End Google Auth 116 | // ---------------------------------------------------------------------------------------- 117 | // Final for Identity 118 | services.AddScoped(); 119 | // Uncomment this if you want use Hangfire 120 | // services.AddHangfire(options => GlobalConfiguration.Configuration.UsePostgreSqlStorage(connectionString)); 121 | // services.AddSingleton(); 122 | } 123 | 124 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 125 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 126 | { 127 | if (env.IsDevelopment()) 128 | { 129 | app.UseDeveloperExceptionPage(); 130 | } 131 | else 132 | { 133 | app.UseExceptionHandler("/Home/Error"); 134 | app.UseHsts(); 135 | } 136 | if (env.IsEnvironment("Testing")) 137 | { 138 | // Create Database 139 | using (var serviceScope = app.ApplicationServices.GetRequiredService() 140 | .CreateScope()) 141 | { 142 | var context = serviceScope.ServiceProvider.GetService(); 143 | context.Database.OpenConnection(); // see Resource #2 link why we do this 144 | context.Database.EnsureDeleted(); 145 | context.Database.EnsureCreated(); 146 | } 147 | } 148 | loggerFactory.AddFile("Logs/NetCoreBootstrapLogs-{Date}.txt", LogLevel.Error); 149 | app.UseHttpsRedirection(); 150 | app.UseStaticFiles(); 151 | app.UseAuthentication(); 152 | app.UseCookiePolicy(); 153 | // Rollbar middelware start 154 | // app.UseRollbarMiddleware(); 155 | // Rollbar middelware end 156 | app.UseMvc(); 157 | app.UseSwagger(); 158 | app.UseSwaggerUI(c => 159 | { 160 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "NetCoreBootstrap API V1"); 161 | }); 162 | using (var serviceScope = app.ApplicationServices.GetRequiredService().CreateScope()) 163 | { 164 | var context = serviceScope.ServiceProvider.GetService(); 165 | context.Database.Migrate(); 166 | } 167 | // Uncomment this to use Mailer 168 | // Mailer.SetAccountConfiguration(Configuration); 169 | // Uncomment this if you want use Hangfire 170 | // app.UseHangfireDashboard(); 171 | // app.UseHangfireServer(new BackgroundJobServerOptions(), null, new PostgreSqlStorage(Configuration["ConnectionString"])); 172 | } 173 | 174 | // Rollbar methods start 175 | // private void ConfigureRollbarSingleton() 176 | // { 177 | // string rollbarAccessToken = Configuration["Rollbar:AccessToken"]; 178 | // string rollbarEnvironment = Configuration["Rollbar:Environment"]; 179 | // RollbarLocator.RollbarInstance 180 | // // minimally required Rollbar configuration: 181 | // .Configure(new RollbarConfig(rollbarAccessToken) { Environment = rollbarEnvironment }) 182 | // // optional step if you would like to monitor Rollbar internal events within your application: 183 | // .InternalEvent += OnRollbarInternalEvent; 184 | // } 185 | 186 | // private static void OnRollbarInternalEvent(object sender, RollbarEventArgs e) 187 | // { 188 | // Console.WriteLine(e.TraceAsString()); 189 | // RollbarApiErrorEventArgs apiErrorEvent = e as RollbarApiErrorEventArgs; 190 | // if (apiErrorEvent != null) 191 | // { 192 | // //TODO: handle/report Rollbar API communication error event... 193 | // return; 194 | // } 195 | // CommunicationEventArgs commEvent = e as CommunicationEventArgs; 196 | // if (commEvent != null) 197 | // { 198 | // //TODO: handle/report Rollbar API communication event... 199 | // return; 200 | // } 201 | // CommunicationErrorEventArgs commErrorEvent = e as CommunicationErrorEventArgs; 202 | // if (commErrorEvent != null) 203 | // { 204 | // //TODO: handle/report basic communication error while attempting to reach Rollbar API service... 205 | // return; 206 | // } 207 | // InternalErrorEventArgs internalErrorEvent = e as InternalErrorEventArgs; 208 | // if (internalErrorEvent != null) 209 | // { 210 | // //TODO: handle/report basic internal error while using the Rollbar Notifier... 211 | // return; 212 | // } 213 | // } 214 | // Rollbar methods end 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/Views/Account/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | @inject IViewLocalizer Localizer 4 | 5 | @{ 6 | ViewData["Title"] = @Localizer["AccessDenied"]; 7 | } 8 | 9 |
10 |

@Localizer["AccessDenied"]

11 |

@Localizer["AccessDeniedDescription"]

12 |
13 | -------------------------------------------------------------------------------- /src/Views/Account/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | @model NetCoreBootstrap.Models.Views.UserManagementViewModel 4 | @inject IViewLocalizer Localizer 5 | 6 |

@Localizer["Profile"]

7 |

@Localizer["ChangePassword"]

8 | @using (Html.BeginForm("EditUser", "Account", FormMethod.Post)) 9 | { 10 |
11 | @Html.HiddenFor(model => model.UserId) 12 |
13 | @Html.LabelFor(model => model.Password) 14 | @Html.EditorFor(model => model.Password) 15 | @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger"}) 16 |
17 |
18 | @Html.LabelFor(model => model.NewPassword) 19 | @Html.EditorFor(model => model.NewPassword) 20 | @Html.ValidationMessageFor(model => model.NewPassword, "", new { @class = "text-danger"}) 21 |
22 |
23 | @Html.LabelFor(model => model.ConfirmNewPassword) 24 | @Html.EditorFor(model => model.ConfirmNewPassword) 25 | @Html.ValidationMessageFor(model => model.ConfirmNewPassword, "", new { @class = "text-danger"}) 26 |
27 |
28 | 29 |
30 | } 31 | -------------------------------------------------------------------------------- /src/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity; 2 | @using NetCoreBootstrap.Models.Views 3 | @using NetCoreBootstrap.Models.Database 4 | @model LoginViewModel 5 | @inject SignInManager SignInManager 6 | 7 | @using (Html.BeginForm("Login", "Account", FormMethod.Post)) 8 | { 9 |
10 |
11 | @Html.LabelFor(model => model.UserName) 12 | @Html.EditorFor(model => model.UserName) 13 | @Html.ValidationMessageFor(model => model.UserName, "", new { @class = "text-danger"}) 14 |
15 |
16 | @Html.LabelFor(model => model.Password) 17 | @Html.EditorFor(model => model.Password) 18 | @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger"}) 19 |
20 |
21 | @Html.LabelFor(model => model.RememberMe) 22 | @Html.EditorFor(model => model.RememberMe) 23 | @Html.ValidationMessageFor(model => model.RememberMe, "", new { @class = "text-danger"}) 24 |
25 |
26 | 27 |
28 | } 29 | @{ 30 | @using (Html.BeginForm("ExternalLogin", "Account", FormMethod.Post)) 31 | { 32 | foreach (var provider in Model.LoginProviders) 33 | { 34 | 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Views/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @using NetCoreBootstrap.Models.Views 2 | @model UserViewModel 3 | @{ 4 | ViewData["Title"] = "User registration"; 5 | } 6 | 7 |

User registration

8 | @using (Html.BeginForm("Register", "Account", FormMethod.Post)) 9 | { 10 |
11 |
12 | @Html.LabelFor(model => model.UserName) 13 | @Html.EditorFor(model => model.UserName) 14 | @Html.ValidationMessageFor(model => model.UserName, "", new { @class = "text-danger"}) 15 |
16 |
17 | @Html.LabelFor(model => model.Email) 18 | @Html.EditorFor(model => model.Email) 19 | @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger"}) 20 |
21 |
22 | @Html.LabelFor(model => model.Password) 23 | @Html.EditorFor(model => model.Password) 24 | @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger"}) 25 |
26 |
27 | @Html.LabelFor(model => model.ConfirmPassword) 28 | @Html.EditorFor(model => model.ConfirmPassword) 29 | @Html.ValidationMessageFor(model => model.ConfirmPassword, "", new { @class = "text-danger"}) 30 |
31 |
32 | 33 |
34 | } 35 | -------------------------------------------------------------------------------- /src/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | 4 | @inject IViewLocalizer Localizer 5 | 6 | @{ 7 | ViewData["Title"] = Localizer["About"]; 8 | } 9 |

@ViewData["Title"].

10 |

@ViewData["Message"]

11 | 12 |

@Localizer["AdditionalInformation"]

13 | -------------------------------------------------------------------------------- /src/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | 4 | @inject IViewLocalizer Localizer 5 | 6 | @{ 7 | ViewData["Title"] = @Localizer["Contact"]; 8 | } 9 |

@ViewData["Title"].

10 |

@ViewData["Message"]

11 | 12 |
13 | One Microsoft Way
14 | Redmond, WA 98052-6399
15 | P: 16 | 425.555.0100 17 |
18 | 19 |
20 | Support: Support@example.com
21 | Marketing: Marketing@example.com 22 |
23 | -------------------------------------------------------------------------------- /src/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | 4 | @inject IViewLocalizer Localizer 5 | 6 | @{ 7 | ViewData["Title"] = Localizer["HomePage"]; 8 | } 9 | 10 | 72 | 73 |
74 |
75 |

Application uses

76 |
    77 |
  • Sample pages using ASP.NET Core MVC
  • 78 |
  • Bower for managing client-side libraries
  • 79 |
  • Theming using Bootstrap
  • 80 |
81 |
82 | 93 | 105 |
106 |

Run & Deploy

107 | 112 |
113 |
114 | -------------------------------------------------------------------------------- /src/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | 8 |

Development Mode

9 |

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

12 |

13 | 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. 14 |

15 | -------------------------------------------------------------------------------- /src/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | @inject IViewLocalizer Localizer 4 | 5 | 6 | 7 | 8 | 9 | 10 | @ViewData["Title"] - NetCoreBootstrap 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 47 |
48 | @RenderBody() 49 |
50 |
51 |

© 2017 - NetCoreBootstrap

52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 73 | 74 | 75 | 76 | @RenderSection("Scripts", required: false) 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using NetCoreBootstrap.Models.Database 3 | @using Microsoft.AspNetCore.Mvc.Localization 4 | @using System.Globalization; 5 | @inject SignInManager SignInManager 6 | @inject UserManager UserManager 7 | @inject IViewLocalizer Localizer 8 | 9 | @{ 10 | var hello = @Localizer["Hello"].Value; 11 | } 12 | 13 | @if (SignInManager.IsSignedIn(User)) 14 | { 15 | 25 | } 26 | else 27 | { 28 | 32 | } -------------------------------------------------------------------------------- /src/Views/Shared/_UserManagementPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using NetCoreBootstrap.Models.Database 2 | @using Microsoft.AspNetCore.Identity 3 | @using Microsoft.AspNetCore.Mvc.Localization 4 | @using System.Globalization; 5 | @inject SignInManager SignInManager 6 | @inject IViewLocalizer Localizer 7 | 8 | @if (SignInManager.IsSignedIn(User)) 9 | { 10 |
  • @Localizer["Users"]
  • 11 |
  • @Localizer["Roles"]
  • 12 |
  • @Localizer["RoleManager"]
  • 13 | } 14 | -------------------------------------------------------------------------------- /src/Views/UserManagement/AddRole.cshtml: -------------------------------------------------------------------------------- 1 | @model NetCoreBootstrap.Models.Views.UserManagementViewModel 2 | 3 |

    Add role to @Html.DisplayFor(model => model.Email)

    4 |
    5 | @using (Html.BeginForm("AddRole", "UserManagement", FormMethod.Post)) 6 | { 7 | @Html.HiddenFor(model => model.UserId) 8 |
    9 | @Html.DropDownListFor(model => model.NewRole, Model.RolesListItem, "Select a role") 10 |
    11 |
    12 | 13 |
    14 | } 15 | -------------------------------------------------------------------------------- /src/Views/UserManagement/CreateRole.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | @model NetCoreBootstrap.Models.Views.UserManagementViewModel 4 | @inject IViewLocalizer Localizer 5 | 6 |

    @Localizer["CreateRole"]

    7 | @using (Html.BeginForm("CreateRole", "UserManagement", FormMethod.Post)) 8 | { 9 | 10 |
    11 | 12 |
    13 | } 14 | -------------------------------------------------------------------------------- /src/Views/UserManagement/EditRole.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | @model NetCoreBootstrap.Models.Views.UserManagementViewModel 4 | @inject IViewLocalizer Localizer 5 | 6 |

    @Localizer["EditRole"]

    7 | @using (Html.BeginForm("EditRole", "UserManagement", FormMethod.Post)) 8 | { 9 | @Html.HiddenFor(model => model.RoleId) 10 | 11 |
    12 | 13 |
    14 | } 15 | -------------------------------------------------------------------------------- /src/Views/UserManagement/RoleManager.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | @model NetCoreBootstrap.Models.Views.RoleManagerViewModel 4 | @inject IViewLocalizer Localizer 5 | 6 |

    @Localizer["RoleManager"]

    7 | 8 |
    9 |

    @Localizer["Users"]

    10 | @Html.ListBox("UserList", Model.UsersListItem, new { @class = "user-list" }) 11 |
    12 | 13 |
    14 |

    @Localizer["RoleManager"]

    15 | 16 |
    17 | -------------------------------------------------------------------------------- /src/Views/UserManagement/Roles.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | @model NetCoreBootstrap.Models.Views.UserManagementViewModel 4 | @inject IViewLocalizer Localizer 5 | 6 | @{ 7 | var newRoleLabel = @Localizer["NewRole"].Value; 8 | var editRoleLabel = @Localizer["EditRole"].Value; 9 | var deleteRoleLabel = @Localizer["DeleteRole"].Value; 10 | } 11 | 12 |

    @Localizer["Roles"]

    13 | @Html.ActionLink(newRoleLabel, "CreateRole", "UserManagement") 14 | @ViewData["Message"] 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | @foreach (var role in Model.Roles) 24 | { 25 | 26 | 27 | 31 | 32 | } 33 | 34 |
    @Localizer["Name"]@Localizer["Actions"]
    @Html.DisplayFor(model => role.Name) 28 | @Html.ActionLink(editRoleLabel, "EditRole", "UserManagement", new { roleId = role.Id }) | 29 | @Html.ActionLink(deleteRoleLabel, "DeleteRole", "UserManagement", new { roleId = role.Id }) 30 |
    35 | -------------------------------------------------------------------------------- /src/Views/UserManagement/Users.cshtml: -------------------------------------------------------------------------------- 1 | @using NetCoreBootstrap.Models.Database 2 | @using Microsoft.AspNetCore.Identity 3 | @model NetCoreBootstrap.Models.Views.UserManagementViewModel 4 | 5 |

    Users

    6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | @foreach (var user in Model.Users) 16 | { 17 | 18 | 19 | 20 | 21 | 22 | } 23 | 24 |
    UsernameEmailRoles
    @Html.DisplayFor(model => user.UserName)@Html.DisplayFor(model => user.Email)@String.Join(",", user.Roles.Select(role => role.Name))
    25 | -------------------------------------------------------------------------------- /src/Views/UserManagement/_EditRoleForm.cshtml: -------------------------------------------------------------------------------- 1 | @model NetCoreBootstrap.Models.Views.UserManagementViewModel 2 | 3 |
    4 | @Html.LabelFor(model => model.Name) 5 | @Html.EditorFor(model => model.Name) 6 | @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger"}) 7 |
    8 | -------------------------------------------------------------------------------- /src/Views/UserManagement/_UserRoles.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Mvc.Localization 2 | @using System.Globalization; 3 | @model NetCoreBootstrap.Models.Views.RoleManagerViewModel 4 | @inject IViewLocalizer Localizer 5 | 6 | @if(Model.Roles.Count > 0) 7 | { 8 | @using (Html.BeginForm("AddRolesToUser", "UserManagement", FormMethod.Post)) 9 | { 10 | @foreach(var role in Model.Roles) 11 | { 12 |
    13 | @Html.CheckBoxFor(model => model.Roles[role.Key], new {}) 14 | @Html.LabelFor(model => model.Roles[role.Key]) 15 |
    16 | } 17 | @Html.HiddenFor(model => model.SelectedUserId) 18 |
    19 | 20 |
    21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using NetCoreBootstrap 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /src/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | }, 10 | "DefaultCulture": "en-US" 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/styles/example-site.min.scss: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}@media screen and (max-width:767px){.carousel-caption{display:none}} 2 | -------------------------------------------------------------------------------- /src/assets/styles/example-site.scss: -------------------------------------------------------------------------------- 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 | /* Set widths on the form inputs since otherwise they're 100% wide */ 14 | input, 15 | select, 16 | textarea { 17 | max-width: 280px; 18 | } 19 | 20 | /* Carousel */ 21 | .carousel-caption p { 22 | font-size: 20px; 23 | line-height: 1.4; 24 | } 25 | 26 | /* Make .svg files in the carousel display properly in older browsers */ 27 | .carousel-inner .item img[src$=".svg"] { 28 | width: 100%; 29 | } 30 | 31 | /* Hide/rearrange for smaller screens */ 32 | @media screen and (max-width: 767px) { 33 | /* Hide captions */ 34 | .carousel-caption { 35 | display: none; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/assets/styles/site.scss: -------------------------------------------------------------------------------- 1 | @import "example-site"; 2 | @import "example-site.min"; 3 | -------------------------------------------------------------------------------- /src/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asp.net", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "3.3.7", 6 | "jquery": "2.2.0", 7 | "jquery-validation": "1.14.0", 8 | "jquery-validation-unobtrusive": "3.2.6" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [{ 4 | "outputFileName": "wwwroot/css/site.css", 5 | // An array of relative input file paths. Globbing patterns supported 6 | "inputFiles": [ 7 | "wwwroot/css/site.css" 8 | ] 9 | }, 10 | { 11 | "outputFileName": "wwwroot/js/site.min.js", 12 | "inputFiles": [ 13 | "wwwroot/js/site.js" 14 | ], 15 | // Optionally specify minification options 16 | "minify": { 17 | "enabled": true, 18 | "renameLocals": true 19 | }, 20 | // Optionally generate .map file 21 | "sourceMap": false 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /src/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | sass = require('gulp-sass'), 3 | gutil = require('gulp-util'); 4 | 5 | gulp.task('sass', function () { 6 | return gulp.src('assets/styles/site.scss') 7 | .pipe( sass( { outputStyle: 'compressed' } ) ) 8 | .pipe(gulp.dest('wwwroot/css')); 9 | }); 10 | 11 | gulp.task('watch:sass', function () { 12 | gulp.watch([ 13 | './assets/styles/*.scss', 14 | '!./bin/**/*', 15 | '!./obj/**/*', 16 | ], { 17 | interval: 250 18 | }, ['sass']).on('change', function (event) { 19 | gutil.log(`File ${event.path} was ${event.type}, running task.`); 20 | }) 21 | }) 22 | 23 | gulp.task( 'default', ['watch:sass'] ); 24 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "gulp": "^4.0.0", 4 | "gulp-concat": "2.6.1", 5 | "gulp-cssmin": "0.1.7", 6 | "gulp-sass": "^3.1.0", 7 | "gulp-uglify": "2.0.1", 8 | "rimraf": "2.6.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}@media screen and (max-width: 767px){.carousel-caption{display:none}}body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}@media screen and (max-width: 767px){.carousel-caption{display:none}} 2 | -------------------------------------------------------------------------------- /src/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/netcore-bootstrap/6a5b49eb424cf46e659767af39da2bf2476793c2/src/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/wwwroot/images/banner1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/banner2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/images/banner3.svg: -------------------------------------------------------------------------------- 1 | banner3b -------------------------------------------------------------------------------- /src/wwwroot/images/banner4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | $(function() 3 | { 4 | $(".user-list").on('change', function(){ 5 | $(".user-roles").load('ViewRoles?userId=' + $(this).val()); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/netcore-bootstrap/6a5b49eb424cf46e659767af39da2bf2476793c2/src/wwwroot/js/site.min.js -------------------------------------------------------------------------------- /src/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 | } -------------------------------------------------------------------------------- /src/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 | -------------------------------------------------------------------------------- /src/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/netcore-bootstrap/6a5b49eb424cf46e659767af39da2bf2476793c2/src/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/netcore-bootstrap/6a5b49eb424cf46e659767af39da2bf2476793c2/src/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/netcore-bootstrap/6a5b49eb424cf46e659767af39da2bf2476793c2/src/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/netcore-bootstrap/6a5b49eb424cf46e659767af39da2bf2476793c2/src/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/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') -------------------------------------------------------------------------------- /src/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 | } -------------------------------------------------------------------------------- /src/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); -------------------------------------------------------------------------------- /src/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 | } -------------------------------------------------------------------------------- /src/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 | -------------------------------------------------------------------------------- /src/wwwroot/lib/jquery-validation/dist/additional-methods.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Validation Plugin - v1.14.0 - 6/30/2015 2 | * http://jqueryvalidation.org/ 3 | * Copyright (c) 2015 Jörn Zaefferer; Licensed MIT */ 4 | !function(a){"function"==typeof define&&define.amd?define(["jquery","./jquery.validate.min"],a):a(jQuery)}(function(a){!function(){function b(a){return a.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ").replace(/[.(),;:!?%#$'\"_+=\/\-“”’]*/g,"")}a.validator.addMethod("maxWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length<=d},a.validator.format("Please enter {0} words or less.")),a.validator.addMethod("minWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length>=d},a.validator.format("Please enter at least {0} words.")),a.validator.addMethod("rangeWords",function(a,c,d){var e=b(a),f=/\b\w+\b/g;return this.optional(c)||e.match(f).length>=d[0]&&e.match(f).length<=d[1]},a.validator.format("Please enter between {0} and {1} words."))}(),a.validator.addMethod("accept",function(b,c,d){var e,f,g="string"==typeof d?d.replace(/\s/g,"").replace(/,/g,"|"):"image/*",h=this.optional(c);if(h)return h;if("file"===a(c).attr("type")&&(g=g.replace(/\*/g,".*"),c.files&&c.files.length))for(e=0;ec;c++)d=h-c,e=f.substring(c,c+1),g+=d*e;return g%11===0},"Please specify a valid bank account number"),a.validator.addMethod("bankorgiroaccountNL",function(b,c){return this.optional(c)||a.validator.methods.bankaccountNL.call(this,b,c)||a.validator.methods.giroaccountNL.call(this,b,c)},"Please specify a valid bank or giro account number"),a.validator.addMethod("bic",function(a,b){return this.optional(b)||/^([A-Z]{6}[A-Z2-9][A-NP-Z1-2])(X{3}|[A-WY-Z0-9][A-Z0-9]{2})?$/.test(a)},"Please specify a valid BIC code"),a.validator.addMethod("cifES",function(a){"use strict";var b,c,d,e,f,g,h=[];if(a=a.toUpperCase(),!a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)"))return!1;for(d=0;9>d;d++)h[d]=parseInt(a.charAt(d),10);for(c=h[2]+h[4]+h[6],e=1;8>e;e+=2)f=(2*h[e]).toString(),g=f.charAt(1),c+=parseInt(f.charAt(0),10)+(""===g?0:parseInt(g,10));return/^[ABCDEFGHJNPQRSUVW]{1}/.test(a)?(c+="",b=10-parseInt(c.charAt(c.length-1),10),a+=b,h[8].toString()===String.fromCharCode(64+b)||h[8].toString()===a.charAt(a.length-1)):!1},"Please specify a valid CIF number."),a.validator.addMethod("cpfBR",function(a){if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var b,c,d,e,f=0;if(b=parseInt(a.substring(9,10),10),c=parseInt(a.substring(10,11),10),d=function(a,b){var c=10*a%11;return(10===c||11===c)&&(c=0),c===b},""===a||"00000000000"===a||"11111111111"===a||"22222222222"===a||"33333333333"===a||"44444444444"===a||"55555555555"===a||"66666666666"===a||"77777777777"===a||"88888888888"===a||"99999999999"===a)return!1;for(e=1;9>=e;e++)f+=parseInt(a.substring(e-1,e),10)*(11-e);if(d(f,b)){for(f=0,e=1;10>=e;e++)f+=parseInt(a.substring(e-1,e),10)*(12-e);return d(f,c)}return!1},"Please specify a valid CPF number"),a.validator.addMethod("creditcardtypes",function(a,b,c){if(/[^0-9\-]+/.test(a))return!1;a=a.replace(/\D/g,"");var d=0;return c.mastercard&&(d|=1),c.visa&&(d|=2),c.amex&&(d|=4),c.dinersclub&&(d|=8),c.enroute&&(d|=16),c.discover&&(d|=32),c.jcb&&(d|=64),c.unknown&&(d|=128),c.all&&(d=255),1&d&&/^(5[12345])/.test(a)?16===a.length:2&d&&/^(4)/.test(a)?16===a.length:4&d&&/^(3[47])/.test(a)?15===a.length:8&d&&/^(3(0[012345]|[68]))/.test(a)?14===a.length:16&d&&/^(2(014|149))/.test(a)?15===a.length:32&d&&/^(6011)/.test(a)?16===a.length:64&d&&/^(3)/.test(a)?16===a.length:64&d&&/^(2131|1800)/.test(a)?15===a.length:128&d?!0:!1},"Please enter a valid credit card number."),a.validator.addMethod("currency",function(a,b,c){var d,e="string"==typeof c,f=e?c:c[0],g=e?!0:c[1];return f=f.replace(/,/g,""),f=g?f+"]":f+"]?",d="^["+f+"([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$",d=new RegExp(d),this.optional(b)||d.test(a)},"Please specify a valid currency"),a.validator.addMethod("dateFA",function(a,b){return this.optional(b)||/^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test(a)},a.validator.messages.date),a.validator.addMethod("dateITA",function(a,b){var c,d,e,f,g,h=!1,i=/^\d{1,2}\/\d{1,2}\/\d{4}$/;return i.test(a)?(c=a.split("/"),d=parseInt(c[0],10),e=parseInt(c[1],10),f=parseInt(c[2],10),g=new Date(Date.UTC(f,e-1,d,12,0,0,0)),h=g.getUTCFullYear()===f&&g.getUTCMonth()===e-1&&g.getUTCDate()===d?!0:!1):h=!1,this.optional(b)||h},a.validator.messages.date),a.validator.addMethod("dateNL",function(a,b){return this.optional(b)||/^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test(a)},a.validator.messages.date),a.validator.addMethod("extension",function(a,b,c){return c="string"==typeof c?c.replace(/,/g,"|"):"png|jpe?g|gif",this.optional(b)||a.match(new RegExp("\\.("+c+")$","i"))},a.validator.format("Please enter a value with a valid extension.")),a.validator.addMethod("giroaccountNL",function(a,b){return this.optional(b)||/^[0-9]{1,7}$/.test(a)},"Please specify a valid giro account number"),a.validator.addMethod("iban",function(a,b){if(this.optional(b))return!0;var c,d,e,f,g,h,i,j,k,l=a.replace(/ /g,"").toUpperCase(),m="",n=!0,o="",p="";if(c=l.substring(0,2),h={AL:"\\d{8}[\\dA-Z]{16}",AD:"\\d{8}[\\dA-Z]{12}",AT:"\\d{16}",AZ:"[\\dA-Z]{4}\\d{20}",BE:"\\d{12}",BH:"[A-Z]{4}[\\dA-Z]{14}",BA:"\\d{16}",BR:"\\d{23}[A-Z][\\dA-Z]",BG:"[A-Z]{4}\\d{6}[\\dA-Z]{8}",CR:"\\d{17}",HR:"\\d{17}",CY:"\\d{8}[\\dA-Z]{16}",CZ:"\\d{20}",DK:"\\d{14}",DO:"[A-Z]{4}\\d{20}",EE:"\\d{16}",FO:"\\d{14}",FI:"\\d{14}",FR:"\\d{10}[\\dA-Z]{11}\\d{2}",GE:"[\\dA-Z]{2}\\d{16}",DE:"\\d{18}",GI:"[A-Z]{4}[\\dA-Z]{15}",GR:"\\d{7}[\\dA-Z]{16}",GL:"\\d{14}",GT:"[\\dA-Z]{4}[\\dA-Z]{20}",HU:"\\d{24}",IS:"\\d{22}",IE:"[\\dA-Z]{4}\\d{14}",IL:"\\d{19}",IT:"[A-Z]\\d{10}[\\dA-Z]{12}",KZ:"\\d{3}[\\dA-Z]{13}",KW:"[A-Z]{4}[\\dA-Z]{22}",LV:"[A-Z]{4}[\\dA-Z]{13}",LB:"\\d{4}[\\dA-Z]{20}",LI:"\\d{5}[\\dA-Z]{12}",LT:"\\d{16}",LU:"\\d{3}[\\dA-Z]{13}",MK:"\\d{3}[\\dA-Z]{10}\\d{2}",MT:"[A-Z]{4}\\d{5}[\\dA-Z]{18}",MR:"\\d{23}",MU:"[A-Z]{4}\\d{19}[A-Z]{3}",MC:"\\d{10}[\\dA-Z]{11}\\d{2}",MD:"[\\dA-Z]{2}\\d{18}",ME:"\\d{18}",NL:"[A-Z]{4}\\d{10}",NO:"\\d{11}",PK:"[\\dA-Z]{4}\\d{16}",PS:"[\\dA-Z]{4}\\d{21}",PL:"\\d{24}",PT:"\\d{21}",RO:"[A-Z]{4}[\\dA-Z]{16}",SM:"[A-Z]\\d{10}[\\dA-Z]{12}",SA:"\\d{2}[\\dA-Z]{18}",RS:"\\d{18}",SK:"\\d{20}",SI:"\\d{15}",ES:"\\d{20}",SE:"\\d{20}",CH:"\\d{5}[\\dA-Z]{12}",TN:"\\d{20}",TR:"\\d{5}[\\dA-Z]{17}",AE:"\\d{3}\\d{16}",GB:"[A-Z]{4}\\d{14}",VG:"[\\dA-Z]{4}\\d{16}"},g=h[c],"undefined"!=typeof g&&(i=new RegExp("^[A-Z]{2}\\d{2}"+g+"$",""),!i.test(l)))return!1;for(d=l.substring(4,l.length)+l.substring(0,4),j=0;j9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/)},"Please specify a valid mobile number"),a.validator.addMethod("nieES",function(a){"use strict";return a=a.toUpperCase(),a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")?/^[T]{1}/.test(a)?a[8]===/^[T]{1}[A-Z0-9]{8}$/.test(a):/^[XYZ]{1}/.test(a)?a[8]==="TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.replace("X","0").replace("Y","1").replace("Z","2").substring(0,8)%23):!1:!1},"Please specify a valid NIE number."),a.validator.addMethod("nifES",function(a){"use strict";return a=a.toUpperCase(),a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")?/^[0-9]{8}[A-Z]{1}$/.test(a)?"TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,0)%23)===a.charAt(8):/^[KLM]{1}/.test(a)?a[8]===String.fromCharCode(64):!1:!1},"Please specify a valid NIF number."),jQuery.validator.addMethod("notEqualTo",function(b,c,d){return this.optional(c)||!a.validator.methods.equalTo.call(this,b,c,d)},"Please enter a different value, values must not be the same."),a.validator.addMethod("nowhitespace",function(a,b){return this.optional(b)||/^\S+$/i.test(a)},"No white space please"),a.validator.addMethod("pattern",function(a,b,c){return this.optional(b)?!0:("string"==typeof c&&(c=new RegExp("^(?:"+c+")$")),c.test(a))},"Invalid format."),a.validator.addMethod("phoneNL",function(a,b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid phone number."),a.validator.addMethod("phoneUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/)},"Please specify a valid phone number"),a.validator.addMethod("phoneUS",function(a,b){return a=a.replace(/\s+/g,""),this.optional(b)||a.length>9&&a.match(/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]([02-9]\d|1[02-9])-?\d{4}$/)},"Please specify a valid phone number"),a.validator.addMethod("phonesUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/)},"Please specify a valid uk phone number"),a.validator.addMethod("postalCodeCA",function(a,b){return this.optional(b)||/^[ABCEGHJKLMNPRSTVXY]\d[A-Z] \d[A-Z]\d$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeBR",function(a,b){return this.optional(b)||/^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test(a)},"Informe um CEP válido."),a.validator.addMethod("postalcodeIT",function(a,b){return this.optional(b)||/^\d{5}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeNL",function(a,b){return this.optional(b)||/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postcodeUK",function(a,b){return this.optional(b)||/^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(a)},"Please specify a valid UK postcode"),a.validator.addMethod("require_from_group",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_req_grp")?f.data("valid_req_grp"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length>=d[0];return f.data("valid_req_grp",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),h},a.validator.format("Please fill at least {0} of these fields.")),a.validator.addMethod("skip_or_fill_minimum",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_skip")?f.data("valid_skip"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length,i=0===h||h>=d[0];return f.data("valid_skip",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),i},a.validator.format("Please either skip these fields or fill at least {0} of them.")),a.validator.addMethod("stateUS",function(a,b,c){var d,e="undefined"==typeof c,f=e||"undefined"==typeof c.caseSensitive?!1:c.caseSensitive,g=e||"undefined"==typeof c.includeTerritories?!1:c.includeTerritories,h=e||"undefined"==typeof c.includeMilitary?!1:c.includeMilitary;return d=g||h?g&&h?"^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":g?"^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":"^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$":"^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$",d=f?new RegExp(d):new RegExp(d,"i"),this.optional(b)||d.test(a)},"Please specify a valid state"),a.validator.addMethod("strippedminlength",function(b,c,d){return a(b).text().length>=d},a.validator.format("Please enter at least {0} characters")),a.validator.addMethod("time",function(a,b){return this.optional(b)||/^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test(a)},"Please enter a valid time, between 00:00 and 23:59"),a.validator.addMethod("time12h",function(a,b){return this.optional(b)||/^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(a)},"Please enter a valid time in 12-hour am/pm format"),a.validator.addMethod("url2",function(a,b){return this.optional(b)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)},a.validator.messages.url),a.validator.addMethod("vinUS",function(a){if(17!==a.length)return!1;var b,c,d,e,f,g,h=["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"],i=[1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9],j=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2],k=0;for(b=0;17>b;b++){if(e=j[b],d=a.slice(b,b+1),8===b&&(g=d),isNaN(d)){for(c=0;c() 25 | .UseConfiguration(configuration)).CreateClient(); 26 | } 27 | 28 | public HttpClient Client 29 | { 30 | get { return this._client; } 31 | } 32 | 33 | [Fact] 34 | public async Task Get() 35 | { 36 | // Act 37 | Random rnd = new Random(); 38 | int id = rnd.Next(1, 1000); 39 | var response = await Client.GetAsync($"/api/v1/homeapi/{id}"); 40 | response.EnsureSuccessStatusCode(); 41 | var responseString = await response.Content.ReadAsStringAsync(); 42 | 43 | // Assert 44 | Assert.Equal($"{id}", responseString); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/NetCoreBootstrap.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | ../src/ca.ruleset 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tools/tools.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------