├── .gitattributes ├── .gitignore ├── Core.sln └── Core ├── Areas └── Admin │ ├── Controllers │ ├── RolesController.cs │ └── UsersController.cs │ └── Views │ ├── Roles │ ├── Create.cshtml │ ├── Edit.cshtml │ └── Index.cshtml │ ├── Shared │ └── _Layout.cshtml │ ├── Users │ ├── Create.cshtml │ ├── Edit.cshtml │ └── Index.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── Components ├── PageSize.cs └── ProductListingViewComponent.cs ├── Controllers ├── AccountController.cs ├── CategoriesController.cs ├── CrudController.cs ├── FormController.cs ├── HomeController.cs └── ProductsController.cs ├── Core.csproj ├── Infrastructure ├── DataContext.cs ├── IdentityContext.cs └── SeedData.cs ├── Migrations ├── 20220530074548_Initial.Designer.cs ├── 20220530074548_Initial.cs ├── DataContextModelSnapshot.cs └── Identity │ ├── 20220608083747_Initial.Designer.cs │ ├── 20220608083747_Initial.cs │ └── IdentityContextModelSnapshot.cs ├── Models ├── Category.cs ├── Product.cs ├── User.cs └── ViewModels │ ├── AuthDetailsViewModel.cs │ ├── LoginViewModel.cs │ ├── ProductViewModel.cs │ ├── RoleViewModel.cs │ ├── UserViewModel.cs │ └── ViewModelFactory.cs ├── Program.cs ├── Properties └── launchSettings.json ├── TagHelpers ├── ContentWrapperTagHelper.cs ├── HighlightTagHelper.cs ├── TableHeadTagHelper.cs └── TrTagHelper.cs ├── Views ├── Account │ ├── Details.cshtml │ └── Login.cshtml ├── Crud │ ├── Index.cshtml │ └── ProductEditor.cshtml ├── Form │ ├── Index.cshtml │ └── Results.cshtml ├── Home │ ├── Fruit.cshtml │ ├── Html.cshtml │ ├── Index.cshtml │ ├── List.cshtml │ ├── _CellPartial.cshtml │ └── _RowPartial.cshtml ├── Shared │ ├── Common.cshtml │ ├── Components │ │ ├── PageSize │ │ │ └── Default.cshtml │ │ └── ProductListing │ │ │ └── Default.cshtml │ ├── _Layout.cshtml │ └── _ValidationScriptsPartial.cshtml ├── _ViewImports.cshtml └── _ViewStart.cshtml ├── appsettings.Development.json ├── appsettings.json ├── coursefiles ├── connection string.txt └── core.jpg ├── libman.json └── wwwroot ├── images └── core.jpg ├── lib ├── bootstrap │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-grid.rtl.css │ │ ├── bootstrap-grid.rtl.css.map │ │ ├── bootstrap-grid.rtl.min.css │ │ ├── bootstrap-grid.rtl.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap-reboot.rtl.css │ │ ├── bootstrap-reboot.rtl.css.map │ │ ├── bootstrap-reboot.rtl.min.css │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ ├── bootstrap-utilities.css │ │ ├── bootstrap-utilities.css.map │ │ ├── bootstrap-utilities.min.css │ │ ├── bootstrap-utilities.min.css.map │ │ ├── bootstrap-utilities.rtl.css │ │ ├── bootstrap-utilities.rtl.css.map │ │ ├── bootstrap-utilities.rtl.min.css │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.css.map │ │ ├── bootstrap.rtl.css │ │ ├── bootstrap.rtl.css.map │ │ ├── bootstrap.rtl.min.css │ │ └── bootstrap.rtl.min.css.map │ ├── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.esm.js │ │ ├── bootstrap.esm.js.map │ │ ├── bootstrap.esm.min.js │ │ ├── bootstrap.esm.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ └── scss │ │ ├── _accordion.scss │ │ ├── _alert.scss │ │ ├── _badge.scss │ │ ├── _breadcrumb.scss │ │ ├── _button-group.scss │ │ ├── _buttons.scss │ │ ├── _card.scss │ │ ├── _carousel.scss │ │ ├── _close.scss │ │ ├── _containers.scss │ │ ├── _dropdown.scss │ │ ├── _forms.scss │ │ ├── _functions.scss │ │ ├── _grid.scss │ │ ├── _helpers.scss │ │ ├── _images.scss │ │ ├── _list-group.scss │ │ ├── _mixins.scss │ │ ├── _modal.scss │ │ ├── _nav.scss │ │ ├── _navbar.scss │ │ ├── _offcanvas.scss │ │ ├── _pagination.scss │ │ ├── _placeholders.scss │ │ ├── _popover.scss │ │ ├── _progress.scss │ │ ├── _reboot.scss │ │ ├── _root.scss │ │ ├── _spinners.scss │ │ ├── _tables.scss │ │ ├── _toasts.scss │ │ ├── _tooltip.scss │ │ ├── _transitions.scss │ │ ├── _type.scss │ │ ├── _utilities.scss │ │ ├── _variables.scss │ │ ├── bootstrap-grid.scss │ │ ├── bootstrap-reboot.scss │ │ ├── bootstrap-utilities.scss │ │ ├── bootstrap.scss │ │ ├── forms │ │ ├── _floating-labels.scss │ │ ├── _form-check.scss │ │ ├── _form-control.scss │ │ ├── _form-range.scss │ │ ├── _form-select.scss │ │ ├── _form-text.scss │ │ ├── _input-group.scss │ │ ├── _labels.scss │ │ └── _validation.scss │ │ ├── helpers │ │ ├── _clearfix.scss │ │ ├── _colored-links.scss │ │ ├── _position.scss │ │ ├── _ratio.scss │ │ ├── _stacks.scss │ │ ├── _stretched-link.scss │ │ ├── _text-truncation.scss │ │ ├── _visually-hidden.scss │ │ └── _vr.scss │ │ ├── mixins │ │ ├── _alert.scss │ │ ├── _backdrop.scss │ │ ├── _border-radius.scss │ │ ├── _box-shadow.scss │ │ ├── _breakpoints.scss │ │ ├── _buttons.scss │ │ ├── _caret.scss │ │ ├── _clearfix.scss │ │ ├── _color-scheme.scss │ │ ├── _container.scss │ │ ├── _deprecate.scss │ │ ├── _forms.scss │ │ ├── _gradients.scss │ │ ├── _grid.scss │ │ ├── _image.scss │ │ ├── _list-group.scss │ │ ├── _lists.scss │ │ ├── _pagination.scss │ │ ├── _reset-text.scss │ │ ├── _resize.scss │ │ ├── _table-variants.scss │ │ ├── _text-truncate.scss │ │ ├── _transition.scss │ │ ├── _utilities.scss │ │ └── _visually-hidden.scss │ │ ├── utilities │ │ └── _api.scss │ │ └── vendor │ │ └── _rfs.scss └── jquery │ ├── jquery.js │ ├── jquery.min.js │ ├── jquery.min.map │ ├── jquery.slim.js │ ├── jquery.slim.min.js │ └── jquery.slim.min.map └── static.html /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Core.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32228.430 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{CED73C3D-449D-406B-A2E5-8AA75B9B8B38}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {CED73C3D-449D-406B-A2E5-8AA75B9B8B38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {CED73C3D-449D-406B-A2E5-8AA75B9B8B38}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {CED73C3D-449D-406B-A2E5-8AA75B9B8B38}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {CED73C3D-449D-406B-A2E5-8AA75B9B8B38}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {20BF76E0-7C67-4F42-9A00-08176D54A990} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Core/Areas/Admin/Controllers/RolesController.cs: -------------------------------------------------------------------------------- 1 | using Core.Models.ViewModels; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.AspNetCore.Mvc; 4 | using System.ComponentModel.DataAnnotations; 5 | 6 | namespace Core.Areas.Admin.Controllers 7 | { 8 | [Area("Admin")] 9 | public class RolesController : Controller 10 | { 11 | private UserManager _userManager; 12 | private RoleManager _roleManager; 13 | 14 | public RolesController(UserManager userManager, RoleManager roleManager) 15 | { 16 | _userManager = userManager; 17 | _roleManager = roleManager; 18 | } 19 | 20 | public IActionResult Index() => View(_roleManager.Roles); 21 | 22 | public IActionResult Create() => View(); 23 | 24 | [HttpPost] 25 | public async Task Create([MinLength(2), Required] string name) 26 | { 27 | if (ModelState.IsValid) 28 | { 29 | IdentityResult result = await _roleManager.CreateAsync(new IdentityRole(name)); 30 | 31 | if (result.Succeeded) 32 | { 33 | return RedirectToAction("Index"); 34 | } 35 | else 36 | { 37 | foreach (IdentityError error in result.Errors) 38 | { 39 | ModelState.AddModelError("", error.Description); 40 | } 41 | } 42 | } 43 | 44 | return View(); 45 | } 46 | 47 | public async Task Edit(string id) 48 | { 49 | IdentityRole role = await _roleManager.FindByIdAsync(id); 50 | 51 | IEnumerable members = await _userManager.GetUsersInRoleAsync(role.Name); 52 | IEnumerable nonMembers = _userManager.Users.ToList().Except(members); 53 | 54 | return View(new RoleViewModel 55 | { 56 | Role = role, 57 | NonMembers = nonMembers, 58 | Members = members 59 | }); 60 | } 61 | 62 | [HttpPost] 63 | public async Task Edit(RoleViewModel roleVM) 64 | { 65 | IdentityResult result; 66 | 67 | foreach (string id in roleVM.AddIds ?? Array.Empty()) 68 | { 69 | IdentityUser user = await _userManager.FindByIdAsync(id); 70 | result = await _userManager.AddToRoleAsync(user, roleVM.RoleName); 71 | } 72 | 73 | foreach (string id in roleVM.DeleteIds ?? new string[] { }) 74 | { 75 | IdentityUser user = await _userManager.FindByIdAsync(id); 76 | result = await _userManager.RemoveFromRoleAsync(user, roleVM.RoleName); 77 | } 78 | 79 | return Redirect(Request.Headers["Referer"].ToString()); 80 | } 81 | 82 | public async Task Delete(string id) 83 | { 84 | IdentityRole role = await _roleManager.FindByIdAsync(id); 85 | 86 | await _roleManager.DeleteAsync(role); 87 | 88 | return RedirectToAction("Index"); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Core/Areas/Admin/Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | using Core.Models; 2 | using Core.Models.ViewModels; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace Core.Areas.Admin.Controllers 7 | { 8 | [Area("Admin")] 9 | public class UsersController : Controller 10 | { 11 | private UserManager _userManager; 12 | 13 | public UsersController(UserManager userManager) 14 | { 15 | _userManager = userManager; 16 | } 17 | 18 | public IActionResult Index() => View(_userManager.Users.ToList()); 19 | 20 | public IActionResult Create() => View(); 21 | 22 | [HttpPost] 23 | public async Task Create(User user) 24 | { 25 | if (ModelState.IsValid) 26 | { 27 | IdentityUser newUser = new IdentityUser { UserName = user.UserName, Email = user.Email }; 28 | IdentityResult result = await _userManager.CreateAsync(newUser, user.Password); 29 | 30 | if (result.Succeeded) 31 | { 32 | return RedirectToAction("Index"); 33 | } 34 | 35 | foreach (IdentityError error in result.Errors) 36 | { 37 | ModelState.AddModelError("", error.Description); 38 | } 39 | 40 | } 41 | 42 | return View(user); 43 | } 44 | 45 | public async Task Edit(string id) 46 | { 47 | IdentityUser user = await _userManager.FindByIdAsync(id); 48 | 49 | UserViewModel userEdit = new(user); 50 | 51 | return View(userEdit); 52 | } 53 | 54 | [HttpPost] 55 | public async Task Edit(UserViewModel user) 56 | { 57 | if (ModelState.IsValid) 58 | { 59 | IdentityUser identityUser = await _userManager.FindByIdAsync(user.Id); 60 | identityUser.UserName = user.UserName; 61 | identityUser.Email = user.Email; 62 | 63 | IdentityResult result = await _userManager.UpdateAsync(identityUser); 64 | 65 | if (result.Succeeded && !String.IsNullOrEmpty(user.Password)) 66 | { 67 | await _userManager.RemovePasswordAsync(identityUser); 68 | result = await _userManager.AddPasswordAsync(identityUser, user.Password); 69 | } 70 | 71 | if (result.Succeeded) 72 | { 73 | return RedirectToAction("Index"); 74 | } 75 | 76 | foreach (IdentityError error in result.Errors) 77 | { 78 | ModelState.AddModelError("", error.Description); 79 | } 80 | 81 | } 82 | 83 | return View(user); 84 | } 85 | 86 | public async Task Delete(string id) 87 | { 88 | IdentityUser user = await _userManager.FindByIdAsync(id); 89 | 90 | if (user != null) 91 | { 92 | await _userManager.DeleteAsync(user); 93 | } 94 | 95 | return RedirectToAction("Index"); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Core/Areas/Admin/Views/Roles/Create.cshtml: -------------------------------------------------------------------------------- 1 | @model User 2 | 3 | @{ 4 | ViewData["Title"] = "Create"; 5 | } 6 | 7 |

Create

8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 |
19 | 20 |
21 | 22 |
23 |
24 |
25 |
26 | 27 |
28 | Back to List 29 |
30 | 31 | -------------------------------------------------------------------------------- /Core/Areas/Admin/Views/Roles/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @model RoleViewModel 2 | 3 |

Edit Role Members: @Model.Role.Name

4 | 5 | 6 |
7 |

Non members

8 | 9 | 10 | 11 | 12 | @if (Model.NonMembers != null && Model.NonMembers.Count() == 0) 13 | { 14 | 15 | 16 | 17 | } 18 | else 19 | { 20 | foreach (IdentityUser user in Model.NonMembers) 21 | { 22 | 23 | 24 | 27 | 28 | } 29 | } 30 |
All users are members.
@user.UserName 25 | 26 |
31 | 32 |

Members

33 | 34 | @if (Model.Members != null && Model.Members.Count() == 0) 35 | { 36 | 37 | 38 | 39 | } 40 | else 41 | { 42 | foreach (IdentityUser user in Model.Members) 43 | { 44 | 45 | 46 | 49 | 50 | } 51 | } 52 |
No users are members.
@user.UserName 47 | 48 |
53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | Back to List 62 |
63 | -------------------------------------------------------------------------------- /Core/Areas/Admin/Views/Roles/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 |

Roles

4 | 5 | Add new role 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @if (Model.Count() == 0) 17 | { 18 | 19 | 20 | 21 | } 22 | else 23 | { 24 | foreach (var role in Model) 25 | { 26 | 27 | 28 | 29 | 30 | 33 | 36 | 37 | } 38 | } 39 |
IdNameMembersEditDelete
No Roles
@role.Id@role.Namemembers 31 | Edit 32 | 34 | Delete 35 |
-------------------------------------------------------------------------------- /Core/Areas/Admin/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Admin 7 | @**@ 8 | 13 | @**@ 14 | 15 | 16 | 17 |
18 | 19 |
20 | @RenderBody() 21 |
22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /Core/Areas/Admin/Views/Users/Create.cshtml: -------------------------------------------------------------------------------- 1 | @model User 2 | 3 | @{ 4 | ViewData["Title"] = "Create"; 5 | } 6 | 7 |

Create

8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 |
39 | 40 |
41 | Back to List 42 |
43 | 44 | -------------------------------------------------------------------------------- /Core/Areas/Admin/Views/Users/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @model UserViewModel 2 | 3 | @{ 4 | ViewData["Title"] = "Edit"; 5 | } 6 | 7 |

Edit

8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 |
32 | 33 | 34 | 35 |
36 | 37 |
38 |
39 |
40 |
41 | 42 |
43 | Back to List 44 |
45 | 46 | -------------------------------------------------------------------------------- /Core/Areas/Admin/Views/Users/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 |

Admin Area

3 | 4 | Add new user 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | @if (Model.Count == 0) 16 | { 17 | 18 | 19 | 20 | } 21 | else 22 | { 23 | foreach (var user in Model) 24 | { 25 | 26 | 27 | 28 | 29 | 32 | 35 | 36 | } 37 | } 38 |
IdUserNameEmailEditDelete
No User Accounts
@user.Id@user.UserName@user.Email 30 | Edit 31 | 33 | Delete 34 |
-------------------------------------------------------------------------------- /Core/Areas/Admin/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Core.Models 2 | @using Core.Models.ViewModels 3 | @using Microsoft.AspNetCore.Identity 4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 5 | @addTagHelper *, Core -------------------------------------------------------------------------------- /Core/Areas/Admin/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Core/Components/PageSize.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace Core.Components 4 | { 5 | [ViewComponent] 6 | public class PageSize : ViewComponent 7 | { 8 | public async Task InvokeAsync() 9 | { 10 | HttpClient client = new(); 11 | HttpResponseMessage response = await client.GetAsync("http://google.com"); 12 | 13 | return View(response.Content.Headers.ContentLength); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Core/Components/ProductListingViewComponent.cs: -------------------------------------------------------------------------------- 1 | using Core.Infrastructure; 2 | using Core.Models; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Core.Components 7 | { 8 | public class ProductListingViewComponent : ViewComponent 9 | { 10 | private DataContext _context; 11 | 12 | public IEnumerable Products; 13 | 14 | public ProductListingViewComponent(DataContext context) 15 | { 16 | _context = context; 17 | } 18 | 19 | public IViewComponentResult Invoke(string className = "primary") 20 | { 21 | ViewBag.Class = className; 22 | return View(_context.Products.Include(p => p.Category).ToList()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Core/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using Core.Models.ViewModels; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace Core.Controllers 7 | { 8 | public class AccountController : Controller 9 | { 10 | private SignInManager _signInManager; 11 | private UserManager _userManager; 12 | 13 | public AccountController(SignInManager signInManager, UserManager userManager) 14 | { 15 | _signInManager = signInManager; 16 | _userManager = userManager; 17 | } 18 | 19 | public IActionResult Login(string returnUrl) => View(new LoginViewModel { ReturnUrl = returnUrl }); 20 | 21 | [HttpPost] 22 | public async Task Login(LoginViewModel loginVM) 23 | { 24 | if (ModelState.IsValid) 25 | { 26 | Microsoft.AspNetCore.Identity.SignInResult result = await _signInManager.PasswordSignInAsync(loginVM.UserName, loginVM.Password, false, false); 27 | 28 | if (result.Succeeded) 29 | { 30 | return Redirect(loginVM.ReturnUrl ?? "/"); 31 | } 32 | 33 | ModelState.AddModelError("", "Invalid username or password"); 34 | } 35 | 36 | return View(loginVM); 37 | } 38 | 39 | 40 | public async Task Details() 41 | { 42 | if (User.Identity != null && User.Identity.IsAuthenticated) 43 | { 44 | IdentityUser user = await _userManager.FindByNameAsync(User.Identity.Name); 45 | 46 | return View(new AuthDetailsViewModel { Cookie = Request.Cookies[".AspNetCore.Identity.Application"], User = user }); 47 | } 48 | 49 | return View(new AuthDetailsViewModel()); 50 | } 51 | 52 | public async Task Logout(string returnUrl = "/") 53 | { 54 | await _signInManager.SignOutAsync(); 55 | 56 | return Redirect(returnUrl); 57 | } 58 | 59 | [Authorize] 60 | public string AllRoles() => "All Roles"; 61 | 62 | [Authorize(Roles = "Admin")] 63 | public string AdminOnly() => "Admin Only"; 64 | 65 | [Authorize(Roles = "Manager")] 66 | public string ManagerOnly() => "Manager Only"; 67 | 68 | public string AccessDenied() => "Access Denied"; 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Core/Controllers/CategoriesController.cs: -------------------------------------------------------------------------------- 1 | using Core.Infrastructure; 2 | using Core.Models; 3 | using Microsoft.AspNetCore.JsonPatch; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace Core.Controllers 8 | { 9 | [ApiController] 10 | [Route("api/[controller]")] 11 | public class CategoriesController : ControllerBase 12 | { 13 | private DataContext _context; 14 | 15 | public CategoriesController(DataContext context) 16 | { 17 | _context = context; 18 | } 19 | 20 | // api/categories/1 21 | [HttpGet("{id}")] 22 | [Produces("application/json", "application/xml")] 23 | public async Task GetCategory(long id) 24 | { 25 | Category category = await _context.Categories.Include(c => c.Products).FirstAsync(c => c.Id == id); 26 | if (category.Products != null) 27 | { 28 | foreach (Product product in category.Products) 29 | { 30 | product.Category = null; 31 | } 32 | } 33 | 34 | return category; 35 | } 36 | 37 | // api/categories/1 38 | [HttpPatch("{id}")] 39 | public async Task PatchCategory(long id, JsonPatchDocument patchDoc) 40 | { 41 | Category category = await _context.Categories.FindAsync(id); 42 | 43 | if (category != null) 44 | { 45 | patchDoc.ApplyTo(category); 46 | await _context.SaveChangesAsync(); 47 | } 48 | 49 | return category; 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Core/Controllers/CrudController.cs: -------------------------------------------------------------------------------- 1 | using Core.Infrastructure; 2 | using Core.Models; 3 | using Core.Models.ViewModels; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace Core.Controllers 8 | { 9 | public class CrudController : Controller 10 | { 11 | private DataContext _context; 12 | 13 | public CrudController(DataContext context) 14 | { 15 | _context = context; 16 | } 17 | 18 | public IActionResult Index() => View(_context.Products.Include(p => p.Category)); 19 | 20 | public async Task Details(long id) 21 | { 22 | Product product = await _context.Products.Include(p => p.Category).FirstOrDefaultAsync(p => p.Id == id); 23 | 24 | ProductViewModel model = ViewModelFactory.Details(product); 25 | 26 | return View("ProductEditor", model); 27 | } 28 | 29 | public IActionResult Create() => View("ProductEditor", ViewModelFactory.Create(new Product(), _context.Categories)); 30 | 31 | [HttpPost] 32 | public async Task Create([FromForm] Product product) 33 | { 34 | if (ModelState.IsValid) 35 | { 36 | _context.Products.Add(product); 37 | await _context.SaveChangesAsync(); 38 | 39 | return RedirectToAction("Index"); 40 | } 41 | 42 | return View("ProductEditor", ViewModelFactory.Create(product, _context.Categories)); 43 | } 44 | 45 | public async Task Edit(long id) 46 | { 47 | Product product = await _context.Products.FindAsync(id); 48 | 49 | if (product != null) 50 | { 51 | ProductViewModel model = ViewModelFactory.Edit(product, _context.Categories); 52 | 53 | return View("ProductEditor", model); 54 | } 55 | 56 | return View("ProductEditor", ViewModelFactory.Create(new Product(), _context.Categories)); 57 | } 58 | 59 | [HttpPost] 60 | public async Task Edit([FromForm] Product product) 61 | { 62 | if (ModelState.IsValid) 63 | { 64 | _context.Products.Update(product); 65 | await _context.SaveChangesAsync(); 66 | 67 | return RedirectToAction("Index"); 68 | } 69 | 70 | return View("ProductEditor", ViewModelFactory.Edit(product, _context.Categories)); 71 | } 72 | 73 | public async Task Delete(Product product) 74 | { 75 | _context.Products.Remove(product); 76 | await _context.SaveChangesAsync(); 77 | 78 | return RedirectToAction("Index"); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Core/Controllers/FormController.cs: -------------------------------------------------------------------------------- 1 | using Core.Infrastructure; 2 | using Core.Models; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.Rendering; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace Core.Controllers 8 | { 9 | [AutoValidateAntiforgeryToken] 10 | public class FormController : Controller 11 | { 12 | private DataContext _context; 13 | 14 | public FormController(DataContext context) 15 | { 16 | _context = context; 17 | } 18 | 19 | public async Task Index([FromQuery] long id = 1) 20 | { 21 | ViewBag.Categories = new SelectList(_context.Categories, "Id", "Name"); 22 | return View(await _context.Products.Include(p => p.Category).FirstAsync(p => p.Id == id)); 23 | } 24 | 25 | [HttpPost] 26 | //public IActionResult SubmitForm(string name, decimal price) 27 | public IActionResult SubmitForm([Bind("Name")] Product product) 28 | { 29 | //TempData["name param"] = name; 30 | //TempData["price param"] = price.ToString(); 31 | 32 | TempData["product"] = System.Text.Json.JsonSerializer.Serialize(product); 33 | 34 | return RedirectToAction("Results"); 35 | } 36 | 37 | public IActionResult Results() 38 | { 39 | return View(); 40 | } 41 | 42 | public string Header([FromHeader(Name = "Accept-Language")] string accept) 43 | { 44 | return $"Header: {accept}"; 45 | } 46 | 47 | [HttpPost] 48 | [IgnoreAntiforgeryToken] 49 | public Product Body([FromBody] Product model) 50 | { 51 | return model; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Core/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Core.Infrastructure; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace Core.Controllers 6 | { 7 | public class HomeController : Controller 8 | { 9 | private DataContext _context; 10 | 11 | public HomeController(DataContext context) 12 | { 13 | _context = context; 14 | } 15 | 16 | public async Task Index(long id = 1) 17 | { 18 | return View(await _context.Products.FindAsync(id)); 19 | } 20 | 21 | public IActionResult Common(long id) 22 | { 23 | return View("/Views/Shared/Common.cshtml"); 24 | } 25 | 26 | public async Task List() 27 | { 28 | ViewBag.AveragePrice = await _context.Products.AverageAsync(p => p.Price); 29 | 30 | return View(await _context.Products.ToListAsync()); 31 | } 32 | 33 | public IActionResult Redirect() 34 | { 35 | TempData["value"] = "TempData value"; 36 | return RedirectToAction("Index", new { id = 1 }); 37 | } 38 | 39 | public IActionResult Html() 40 | { 41 | return View((object)"This is a

string

"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Core/Controllers/ProductsController.cs: -------------------------------------------------------------------------------- 1 | using Core.Infrastructure; 2 | using Core.Models; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Core.Controllers 6 | { 7 | [ApiController] 8 | [Route("api/[controller]")] 9 | public class ProductsController : ControllerBase 10 | { 11 | private DataContext _context; 12 | 13 | public ProductsController(DataContext context) 14 | { 15 | _context = context; 16 | } 17 | 18 | // api/products 19 | [HttpGet] 20 | public IAsyncEnumerable GetProducts() 21 | { 22 | return _context.Products.AsAsyncEnumerable(); 23 | } 24 | 25 | // api/products/1 26 | [HttpGet("{id}")] 27 | public async Task GetProduct(long id) 28 | { 29 | Product product = await _context.Products.FindAsync(id); 30 | 31 | if (product == null) 32 | { 33 | return NotFound(); 34 | } 35 | return Ok(product); 36 | 37 | } 38 | 39 | // api/products 40 | [HttpPost] 41 | [Consumes("application/xml")] 42 | public async Task SaveProduct([FromBody] Product product) 43 | { 44 | await _context.Products.AddAsync(product); 45 | await _context.SaveChangesAsync(); 46 | 47 | return Ok(product); 48 | } 49 | 50 | // api/products 51 | [HttpPut] 52 | public void UpdateProduct(Product product) 53 | { 54 | _context.Update(product); 55 | _context.SaveChanges(); 56 | } 57 | 58 | // api/products/1 59 | [HttpDelete("{id}")] 60 | public void DeleteProduct(long id) 61 | { 62 | _context.Products.Remove(new Product { Id = id }); 63 | _context.SaveChanges(); 64 | } 65 | 66 | // api/products/redirect 67 | [HttpGet("redirect")] 68 | public IActionResult Redirect() 69 | { 70 | //return Redirect("/api/products/1"); 71 | //return RedirectToAction(nameof(GetProduct), new { Id = 1 }); 72 | return RedirectToRoute(new 73 | { 74 | controller = "Products", 75 | action = "GetProduct", 76 | Id = 1 77 | }); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Core/Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | disable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Core/Infrastructure/DataContext.cs: -------------------------------------------------------------------------------- 1 | using Core.Models; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace Core.Infrastructure 5 | { 6 | public class DataContext : DbContext 7 | { 8 | public DataContext(DbContextOptions options) : base(options) { } 9 | 10 | public DbSet Products { get; set; } 11 | public DbSet Categories { get; set; } 12 | public DbSet User { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Core/Infrastructure/IdentityContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace Core.Infrastructure 6 | { 7 | public class IdentityContext : IdentityDbContext 8 | { 9 | public IdentityContext(DbContextOptions options) : base(options) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Core/Infrastructure/SeedData.cs: -------------------------------------------------------------------------------- 1 | using Core.Models; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace Core.Infrastructure 5 | { 6 | public class SeedData 7 | { 8 | public static void SeedDatabase(DataContext context) 9 | { 10 | context.Database.Migrate(); 11 | 12 | if (context.Products.Count() == 0 && context.Categories.Count() == 0) 13 | { 14 | Category fruits = new Category { Name = "fruits" }; 15 | Category shirts = new Category { Name = "shirts" }; 16 | 17 | context.Products.AddRange( 18 | new Product 19 | { 20 | Name = "Apples", 21 | Price = 1.50M, 22 | Category = fruits 23 | }, 24 | new Product 25 | { 26 | Name = "Lemons", 27 | Price = 2M, 28 | Category = fruits 29 | }, 30 | new Product 31 | { 32 | Name = "Watermelon", 33 | Price = 0.50M, 34 | Category = fruits 35 | }, 36 | new Product 37 | { 38 | Name = "Grapefruit", 39 | Price = 2.50M, 40 | Category = fruits 41 | }, 42 | new Product 43 | { 44 | Name = "Blue shirt", 45 | Price = 5.99M, 46 | Category = shirts 47 | }, 48 | new Product 49 | { 50 | Name = "Black shirt", 51 | Price = 7.99M, 52 | Category = shirts 53 | }, 54 | new Product 55 | { 56 | Name = "Red shirt", 57 | Price = 9.99M, 58 | Category = shirts 59 | }, 60 | new Product 61 | { 62 | Name = "Yellow shirt", 63 | Price = 11.99M, 64 | Category = shirts 65 | } 66 | ); 67 | 68 | context.SaveChanges(); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Core/Migrations/20220530074548_Initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Core.Infrastructure; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | #nullable disable 10 | 11 | namespace Core.Migrations 12 | { 13 | [DbContext(typeof(DataContext))] 14 | [Migration("20220530074548_Initial")] 15 | partial class Initial 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "6.0.5") 22 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 23 | 24 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 25 | 26 | modelBuilder.Entity("Core.Models.Category", b => 27 | { 28 | b.Property("Id") 29 | .ValueGeneratedOnAdd() 30 | .HasColumnType("bigint"); 31 | 32 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 33 | 34 | b.Property("Name") 35 | .HasColumnType("nvarchar(max)"); 36 | 37 | b.HasKey("Id"); 38 | 39 | b.ToTable("Categories"); 40 | }); 41 | 42 | modelBuilder.Entity("Core.Models.Product", b => 43 | { 44 | b.Property("Id") 45 | .ValueGeneratedOnAdd() 46 | .HasColumnType("bigint"); 47 | 48 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 49 | 50 | b.Property("CategoryId") 51 | .HasColumnType("bigint"); 52 | 53 | b.Property("Name") 54 | .HasColumnType("nvarchar(max)"); 55 | 56 | b.Property("Price") 57 | .HasColumnType("decimal(8,2)"); 58 | 59 | b.HasKey("Id"); 60 | 61 | b.HasIndex("CategoryId"); 62 | 63 | b.ToTable("Products"); 64 | }); 65 | 66 | modelBuilder.Entity("Core.Models.Product", b => 67 | { 68 | b.HasOne("Core.Models.Category", "Category") 69 | .WithMany("Products") 70 | .HasForeignKey("CategoryId") 71 | .OnDelete(DeleteBehavior.Cascade) 72 | .IsRequired(); 73 | 74 | b.Navigation("Category"); 75 | }); 76 | 77 | modelBuilder.Entity("Core.Models.Category", b => 78 | { 79 | b.Navigation("Products"); 80 | }); 81 | #pragma warning restore 612, 618 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Core/Migrations/20220530074548_Initial.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Core.Migrations 6 | { 7 | public partial class Initial : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "Categories", 13 | columns: table => new 14 | { 15 | Id = table.Column(type: "bigint", nullable: false) 16 | .Annotation("SqlServer:Identity", "1, 1"), 17 | Name = table.Column(type: "nvarchar(max)", nullable: true) 18 | }, 19 | constraints: table => 20 | { 21 | table.PrimaryKey("PK_Categories", x => x.Id); 22 | }); 23 | 24 | migrationBuilder.CreateTable( 25 | name: "Products", 26 | columns: table => new 27 | { 28 | Id = table.Column(type: "bigint", nullable: false) 29 | .Annotation("SqlServer:Identity", "1, 1"), 30 | Name = table.Column(type: "nvarchar(max)", nullable: true), 31 | Price = table.Column(type: "decimal(8,2)", nullable: false), 32 | CategoryId = table.Column(type: "bigint", nullable: false) 33 | }, 34 | constraints: table => 35 | { 36 | table.PrimaryKey("PK_Products", x => x.Id); 37 | table.ForeignKey( 38 | name: "FK_Products_Categories_CategoryId", 39 | column: x => x.CategoryId, 40 | principalTable: "Categories", 41 | principalColumn: "Id", 42 | onDelete: ReferentialAction.Cascade); 43 | }); 44 | 45 | migrationBuilder.CreateIndex( 46 | name: "IX_Products_CategoryId", 47 | table: "Products", 48 | column: "CategoryId"); 49 | } 50 | 51 | protected override void Down(MigrationBuilder migrationBuilder) 52 | { 53 | migrationBuilder.DropTable( 54 | name: "Products"); 55 | 56 | migrationBuilder.DropTable( 57 | name: "Categories"); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Core/Migrations/DataContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Core.Infrastructure; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | 8 | #nullable disable 9 | 10 | namespace Core.Migrations 11 | { 12 | [DbContext(typeof(DataContext))] 13 | partial class DataContextModelSnapshot : ModelSnapshot 14 | { 15 | protected override void BuildModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder 19 | .HasAnnotation("ProductVersion", "6.0.5") 20 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 21 | 22 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 23 | 24 | modelBuilder.Entity("Core.Models.Category", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd() 28 | .HasColumnType("bigint"); 29 | 30 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 31 | 32 | b.Property("Name") 33 | .HasColumnType("nvarchar(max)"); 34 | 35 | b.HasKey("Id"); 36 | 37 | b.ToTable("Categories"); 38 | }); 39 | 40 | modelBuilder.Entity("Core.Models.Product", b => 41 | { 42 | b.Property("Id") 43 | .ValueGeneratedOnAdd() 44 | .HasColumnType("bigint"); 45 | 46 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 47 | 48 | b.Property("CategoryId") 49 | .HasColumnType("bigint"); 50 | 51 | b.Property("Name") 52 | .HasColumnType("nvarchar(max)"); 53 | 54 | b.Property("Price") 55 | .HasColumnType("decimal(8,2)"); 56 | 57 | b.HasKey("Id"); 58 | 59 | b.HasIndex("CategoryId"); 60 | 61 | b.ToTable("Products"); 62 | }); 63 | 64 | modelBuilder.Entity("Core.Models.Product", b => 65 | { 66 | b.HasOne("Core.Models.Category", "Category") 67 | .WithMany("Products") 68 | .HasForeignKey("CategoryId") 69 | .OnDelete(DeleteBehavior.Cascade) 70 | .IsRequired(); 71 | 72 | b.Navigation("Category"); 73 | }); 74 | 75 | modelBuilder.Entity("Core.Models.Category", b => 76 | { 77 | b.Navigation("Products"); 78 | }); 79 | #pragma warning restore 612, 618 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Core/Models/Category.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Models 2 | { 3 | public class Category 4 | { 5 | public long Id { get; set; } 6 | public string Name { get; set; } 7 | 8 | public IEnumerable Products { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Core/Models/Product.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace Core.Models 5 | { 6 | public class Product 7 | { 8 | public long Id { get; set; } 9 | [Required(ErrorMessage = "Please enter a value")] 10 | public string Name { get; set; } 11 | [Required] 12 | [Range(0.01, double.MaxValue, ErrorMessage = "Please enter a value")] 13 | [Column(TypeName = "decimal(8, 2)")] 14 | public decimal Price { get; set; } 15 | 16 | [Required] 17 | [Range(1, long.MaxValue, ErrorMessage = "Please enter a value")] 18 | public long CategoryId { get; set; } 19 | public Category Category { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Core/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Core.Models 4 | { 5 | public class User 6 | { 7 | public string Id { get; set; } 8 | 9 | [Required, MinLength(2, ErrorMessage = "Minimum length is 2")] 10 | [Display(Name = "Username")] 11 | public string UserName { get; set; } 12 | [Required, EmailAddress] 13 | public string Email { get; set; } 14 | [DataType(DataType.Password), Required, MinLength(4, ErrorMessage = "Minimum length is 4")] 15 | public string Password { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Core/Models/ViewModels/AuthDetailsViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace Core.Models.ViewModels 4 | { 5 | public class AuthDetailsViewModel 6 | { 7 | public string Cookie { get; set; } 8 | public IdentityUser User { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Core/Models/ViewModels/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Core.Models.ViewModels 4 | { 5 | public class LoginViewModel 6 | { 7 | [Required, MinLength(2, ErrorMessage = "Minimum length is 2")] 8 | [Display(Name = "Username")] 9 | public string UserName { get; set; } 10 | 11 | [DataType(DataType.Password), Required, MinLength(4, ErrorMessage = "Minimum length is 4")] 12 | public string Password { get; set; } 13 | 14 | public string ReturnUrl { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Core/Models/ViewModels/ProductViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Models.ViewModels 2 | { 3 | public class ProductViewModel 4 | { 5 | public Product Product { get; set; } 6 | public string Action { get; set; } = "Create"; 7 | public bool ReadOnly { get; set; } = false; 8 | public string Theme { get; set; } = "primary"; 9 | public bool ShowAction { get; set; } = true; 10 | public IEnumerable Categories { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Core/Models/ViewModels/RoleViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace Core.Models.ViewModels 4 | { 5 | public class RoleViewModel 6 | { 7 | public IdentityRole Role { get; set; } 8 | 9 | public IEnumerable Members { get; set; } 10 | public IEnumerable NonMembers { get; set; } 11 | 12 | public string RoleName { get; set; } 13 | public string[] AddIds { get; set; } 14 | public string[] DeleteIds { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Core/Models/ViewModels/UserViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Core.Models.ViewModels 5 | { 6 | public class UserViewModel 7 | { 8 | public string Id { get; set; } 9 | 10 | [Required, MinLength(2, ErrorMessage = "Minimum length is 2")] 11 | [Display(Name = "Username")] 12 | public string UserName { get; set; } 13 | [Required, EmailAddress] 14 | public string Email { get; set; } 15 | [DataType(DataType.Password), Required, MinLength(4, ErrorMessage = "Minimum length is 4")] 16 | public string Password { get; set; } 17 | 18 | public UserViewModel() 19 | { 20 | } 21 | 22 | public UserViewModel(IdentityUser user) 23 | { 24 | Id = user.Id; 25 | UserName = user.UserName; 26 | Email = user.Email; 27 | Password = user.PasswordHash; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Core/Models/ViewModels/ViewModelFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Models.ViewModels 2 | { 3 | public static class ViewModelFactory 4 | { 5 | public static ProductViewModel Details(Product product) 6 | { 7 | return new ProductViewModel 8 | { 9 | Product = product, 10 | Action = "Details", 11 | ReadOnly = true, 12 | Theme = "info", 13 | ShowAction = false, 14 | Categories = new List { product.Category } 15 | }; 16 | } 17 | 18 | public static ProductViewModel Create(Product product, IEnumerable categories) 19 | { 20 | return new ProductViewModel 21 | { 22 | Product = product, 23 | Categories = categories 24 | }; 25 | } 26 | 27 | public static ProductViewModel Edit(Product product, IEnumerable categories) 28 | { 29 | return new ProductViewModel 30 | { 31 | Product = product, 32 | Categories = categories, 33 | Theme = "warning", 34 | Action = "Edit" 35 | }; 36 | } 37 | 38 | public static ProductViewModel Delete(Product product, IEnumerable categories) 39 | { 40 | return new ProductViewModel 41 | { 42 | Product = product, 43 | Categories = categories, 44 | Theme = "danger", 45 | Action = "Delete", 46 | ReadOnly = true 47 | }; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Core/Program.cs: -------------------------------------------------------------------------------- 1 | using Core.Infrastructure; 2 | using Microsoft.AspNetCore.Authentication.Cookies; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | builder.Services.AddDbContext(options => 9 | { 10 | options.UseSqlServer(builder.Configuration["ConnectionStrings:DbConnection"]); 11 | }); 12 | 13 | builder.Services.AddDbContext(options => 14 | { 15 | options.UseSqlServer(builder.Configuration["ConnectionStrings:DbConnection"]); 16 | }); 17 | builder.Services.AddIdentity().AddEntityFrameworkStores().AddDefaultTokenProviders(); 18 | builder.Services.Configure(options => 19 | { 20 | options.Password.RequiredLength = 4; 21 | options.Password.RequireNonAlphanumeric = false; 22 | options.Password.RequireLowercase = false; 23 | options.Password.RequireUppercase = false; 24 | options.Password.RequireDigit = false; 25 | 26 | options.User.RequireUniqueEmail = true; 27 | }); 28 | 29 | builder.Services.Configure(IdentityConstants.ApplicationScheme, options => 30 | { 31 | options.LoginPath = "/Login2"; 32 | options.AccessDeniedPath = "/AccessDenied2"; 33 | }); 34 | 35 | builder.Services.AddControllersWithViews(); 36 | 37 | var app = builder.Build(); 38 | 39 | app.MapControllers(); 40 | 41 | app.MapControllerRoute( 42 | name: "Areas", 43 | pattern: "{area:exists}/{controller=Users}/{action=Index}/{id?}" 44 | ); 45 | 46 | app.MapDefaultControllerRoute(); 47 | 48 | app.UseStaticFiles(); 49 | 50 | app.UseAuthentication(); 51 | app.UseAuthorization(); 52 | 53 | var context = app.Services.CreateScope().ServiceProvider.GetRequiredService(); 54 | SeedData.SeedDatabase(context); 55 | 56 | app.Run(); -------------------------------------------------------------------------------- /Core/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:64541", 7 | "sslPort": 44397 8 | } 9 | }, 10 | "profiles": { 11 | "Core": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": false, 15 | "applicationUrl": "https://localhost:4000;http://localhost:3000", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "IIS Express": { 21 | "commandName": "IISExpress", 22 | "launchBrowser": true, 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Core/TagHelpers/ContentWrapperTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Rendering; 2 | using Microsoft.AspNetCore.Razor.TagHelpers; 3 | 4 | namespace Core.TagHelpers 5 | { 6 | [HtmlTargetElement("*", Attributes = "[wrap=true]")] 7 | public class ContentWrapperTagHelper : TagHelper 8 | { 9 | public string WrapperString { get; set; } 10 | 11 | public override void Process(TagHelperContext context, TagHelperOutput output) 12 | { 13 | TagBuilder elem = new TagBuilder("div"); 14 | elem.Attributes["class"] = "bg-primary text-white p-2 m-2"; 15 | elem.InnerHtml.AppendHtml(WrapperString); 16 | 17 | output.PreElement.AppendHtml(elem); 18 | output.PostElement.AppendHtml(elem); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Core/TagHelpers/HighlightTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | 3 | namespace Core.TagHelpers 4 | { 5 | [HtmlTargetElement("*", Attributes = "[highlight=true]")] 6 | public class HighlightTagHelper : TagHelper 7 | { 8 | public override void Process(TagHelperContext context, TagHelperOutput output) 9 | { 10 | output.PreContent.SetHtmlContent(""); 11 | output.PostContent.SetHtmlContent(""); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Core/TagHelpers/TableHeadTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Rendering; 2 | using Microsoft.AspNetCore.Razor.TagHelpers; 3 | 4 | namespace Core.TagHelpers 5 | { 6 | [HtmlTargetElement("tablehead")] 7 | public class TableHeadTagHelper : TagHelper 8 | { 9 | public string BgColor { get; set; } = "dark"; 10 | 11 | public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 12 | { 13 | output.TagName = "thead"; 14 | output.TagMode = TagMode.StartTagAndEndTag; 15 | 16 | output.Attributes.SetAttribute("class", $"bg-{BgColor} text-center text-white"); 17 | 18 | string content = (await output.GetChildContentAsync()).GetContent(); 19 | 20 | //output.Content.SetHtmlContent($"{content}"); 21 | 22 | TagBuilder header = new TagBuilder("th"); 23 | header.Attributes["colspan"] = "3"; 24 | header.InnerHtml.Append(content); 25 | 26 | TagBuilder row = new TagBuilder("tr"); 27 | row.InnerHtml.AppendHtml(header); 28 | 29 | output.Content.SetHtmlContent(row); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Core/TagHelpers/TrTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | 3 | namespace Core.TagHelpers 4 | { 5 | [HtmlTargetElement("tr", Attributes = "bg-color, text-color")] 6 | [HtmlTargetElement("td", Attributes = "bg-color")] 7 | public class TrTagHelper : TagHelper 8 | { 9 | public string BgColor { get; set; } = "dark"; 10 | public string TextColor { get; set; } = "white"; 11 | 12 | public override void Process(TagHelperContext context, TagHelperOutput output) 13 | { 14 | output.Attributes.SetAttribute("class", $"bg-{BgColor} text-center text-{TextColor}"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Core/Views/Account/Details.cshtml: -------------------------------------------------------------------------------- 1 | @model AuthDetailsViewModel 2 | 3 | @{ 4 | ViewData["Title"] = "Auth Details"; 5 | } 6 | 7 |

Auth Details

8 | 9 | 10 | @if (Model.User == null) 11 | { 12 | 13 | 14 | 15 | } else 16 | { 17 | 18 | 19 | 20 | } 21 |
No user
@Model.Cookie
@Model.User.UserName
@Model.User.Email
-------------------------------------------------------------------------------- /Core/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @model LoginViewModel 2 | 3 | @{ 4 | ViewData["Title"] = "Login"; 5 | } 6 | 7 |

Login

8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /Core/Views/Crud/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 | @{ 4 | ViewData["Title"] = "Products"; 5 | } 6 | 7 |

Index

8 | 9 |

10 | Create New 11 |

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | @foreach (var product in Model) 25 | { 26 | 27 | 28 | 29 | 30 | 31 | 36 | 37 | } 38 | 39 |
Id NamePriceCategory
@product.Id@product.Name@product.Price@product.Category.Name 32 | Edit | 33 | Details | 34 | Delete 35 |
40 | -------------------------------------------------------------------------------- /Core/Views/Crud/ProductEditor.cshtml: -------------------------------------------------------------------------------- 1 | @model ProductViewModel 2 | 3 | @{ 4 | ViewData["Title"] = "Product Editor"; 5 | } 6 | 7 | Back to List 8 | 9 |

Product Editor

10 | 11 |
@Model.Action
12 | 13 |
14 |
15 | 16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 | 40 | 41 |
42 | 43 | @if (Model.ShowAction == true) 44 | { 45 | 46 | } 47 | 48 |
-------------------------------------------------------------------------------- /Core/Views/Form/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model Product 2 | 3 | @{ 4 | ViewBag.Title = "Form"; 5 | } 6 | 7 |

Form

8 | 9 |
10 |
11 | 12 | 13 |
14 | 15 |
16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 | 31 |
32 | 33 | 36 | -------------------------------------------------------------------------------- /Core/Views/Form/Results.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Results"; 3 | } 4 | 5 |

Results

6 | 7 | 8 | 9 | 10 | 11 | @foreach (string key in TempData.Keys) 12 | { 13 | 14 | 15 | 16 | 17 | } 18 | 19 |
Form Data
@key@TempData[key]
20 | 21 |
22 | Go back -------------------------------------------------------------------------------- /Core/Views/Home/Fruit.cshtml: -------------------------------------------------------------------------------- 1 | @model Product 2 | 3 | @{ 4 | ViewBag.Title = "Fruit"; 5 | } 6 | 7 |
8 |

Product

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
Name@Model.Name
Price@Model.Price.ToString("c")
22 |
23 | -------------------------------------------------------------------------------- /Core/Views/Home/Html.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 |
4 | @Model 5 |
6 |
7 | @Html.Raw(Model) 8 |
-------------------------------------------------------------------------------- /Core/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model Product 2 | 3 | @{ 4 | ViewBag.Title = "Home"; 5 | } 6 | 7 |
8 |

Product

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
NamePrice
@Model.Name@Model.Price.ToString("C2")
24 | 25 |
26 | Back to List 27 |
28 | 29 |
-------------------------------------------------------------------------------- /Core/Views/Home/List.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 | @{ 4 | decimal average = Model.Average(p => p.Price); 5 | ViewBag.Title = "List"; 6 | ViewData["fruit"] = "apples"; 7 | } 8 | 9 |
10 |

All Products

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | @foreach (Product p in Model) 21 | { 22 | 23 | } 24 | 25 |
NamePrice
26 |
27 | -------------------------------------------------------------------------------- /Core/Views/Home/_CellPartial.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 | @Model -------------------------------------------------------------------------------- /Core/Views/Home/_RowPartial.cshtml: -------------------------------------------------------------------------------- 1 | @model Product 2 | 3 | 4 | 5 | @Model.Price 6 | 7 | 8 | Details 9 | 10 | 11 | -------------------------------------------------------------------------------- /Core/Views/Shared/Common.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |

Shared View

12 |
13 |
14 | 15 | -------------------------------------------------------------------------------- /Core/Views/Shared/Components/PageSize/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model long 2 | 3 |

Page size: @Model

-------------------------------------------------------------------------------- /Core/Views/Shared/Components/ProductListing/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 |
4 |

All Products

5 | 6 | 7 | Product Summary 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @foreach (Product p in Model) 17 | { 18 | 19 | 20 | 21 | 22 | 23 | } 24 | 25 |
NamePriceCategory
@p.Name@p.Price@p.Category.Name
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
Fruit
Apples
36 | 37 |

Oranges

38 | 39 |
Inner Content
-------------------------------------------------------------------------------- /Core/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | @**@ 8 | 13 | @**@ 14 | 15 | 16 | 17 |
18 | 19 |
20 | @RenderBody() 21 |
22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /Core/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /Core/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Core.Models 2 | @using Core.Models.ViewModels 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @addTagHelper *, Core -------------------------------------------------------------------------------- /Core/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Core/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Core/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "ConnectionStrings": { 10 | "DbConnection": "Server=(localdb)\\MSSQLLocalDB;Database=CoreDB;MultipleActiveResultSets=True" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Core/coursefiles/connection string.txt: -------------------------------------------------------------------------------- 1 | "ConnectionStrings": { 2 | "DbConnection": "Server=(localdb)\\MSSQLLocalDB;Database=;MultipleActiveResultSets=True" 3 | } -------------------------------------------------------------------------------- /Core/coursefiles/core.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fep-coder/asp.net-core-6-.net-6-for-beginners/93afb1937d2cc3a4b98a9950260a7bda5fa7d7c6/Core/coursefiles/core.jpg -------------------------------------------------------------------------------- /Core/libman.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "defaultProvider": "cdnjs", 4 | "libraries": [ 5 | { 6 | "library": "bootstrap@5.1.3", 7 | "destination": "wwwroot/lib/bootstrap/" 8 | }, 9 | { 10 | "library": "jquery@3.6.0", 11 | "destination": "wwwroot/lib/jquery/" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /Core/wwwroot/images/core.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fep-coder/asp.net-core-6-.net-6-for-beginners/93afb1937d2cc3a4b98a9950260a7bda5fa7d7c6/Core/wwwroot/images/core.jpg -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_accordion.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Base styles 3 | // 4 | 5 | .accordion-button { 6 | position: relative; 7 | display: flex; 8 | align-items: center; 9 | width: 100%; 10 | padding: $accordion-button-padding-y $accordion-button-padding-x; 11 | @include font-size($font-size-base); 12 | color: $accordion-button-color; 13 | text-align: left; // Reset button style 14 | background-color: $accordion-button-bg; 15 | border: 0; 16 | @include border-radius(0); 17 | overflow-anchor: none; 18 | @include transition($accordion-transition); 19 | 20 | &:not(.collapsed) { 21 | color: $accordion-button-active-color; 22 | background-color: $accordion-button-active-bg; 23 | box-shadow: inset 0 ($accordion-border-width * -1) 0 $accordion-border-color; 24 | 25 | &::after { 26 | background-image: escape-svg($accordion-button-active-icon); 27 | transform: $accordion-icon-transform; 28 | } 29 | } 30 | 31 | // Accordion icon 32 | &::after { 33 | flex-shrink: 0; 34 | width: $accordion-icon-width; 35 | height: $accordion-icon-width; 36 | margin-left: auto; 37 | content: ""; 38 | background-image: escape-svg($accordion-button-icon); 39 | background-repeat: no-repeat; 40 | background-size: $accordion-icon-width; 41 | @include transition($accordion-icon-transition); 42 | } 43 | 44 | &:hover { 45 | z-index: 2; 46 | } 47 | 48 | &:focus { 49 | z-index: 3; 50 | border-color: $accordion-button-focus-border-color; 51 | outline: 0; 52 | box-shadow: $accordion-button-focus-box-shadow; 53 | } 54 | } 55 | 56 | .accordion-header { 57 | margin-bottom: 0; 58 | } 59 | 60 | .accordion-item { 61 | background-color: $accordion-bg; 62 | border: $accordion-border-width solid $accordion-border-color; 63 | 64 | &:first-of-type { 65 | @include border-top-radius($accordion-border-radius); 66 | 67 | .accordion-button { 68 | @include border-top-radius($accordion-inner-border-radius); 69 | } 70 | } 71 | 72 | &:not(:first-of-type) { 73 | border-top: 0; 74 | } 75 | 76 | // Only set a border-radius on the last item if the accordion is collapsed 77 | &:last-of-type { 78 | @include border-bottom-radius($accordion-border-radius); 79 | 80 | .accordion-button { 81 | &.collapsed { 82 | @include border-bottom-radius($accordion-inner-border-radius); 83 | } 84 | } 85 | 86 | .accordion-collapse { 87 | @include border-bottom-radius($accordion-border-radius); 88 | } 89 | } 90 | } 91 | 92 | .accordion-body { 93 | padding: $accordion-body-padding-y $accordion-body-padding-x; 94 | } 95 | 96 | 97 | // Flush accordion items 98 | // 99 | // Remove borders and border-radius to keep accordion items edge-to-edge. 100 | 101 | .accordion-flush { 102 | .accordion-collapse { 103 | border-width: 0; 104 | } 105 | 106 | .accordion-item { 107 | border-right: 0; 108 | border-left: 0; 109 | @include border-radius(0); 110 | 111 | &:first-child { border-top: 0; } 112 | &:last-child { border-bottom: 0; } 113 | 114 | .accordion-button { 115 | @include border-radius(0); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_alert.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Base styles 3 | // 4 | 5 | .alert { 6 | position: relative; 7 | padding: $alert-padding-y $alert-padding-x; 8 | margin-bottom: $alert-margin-bottom; 9 | border: $alert-border-width solid transparent; 10 | @include border-radius($alert-border-radius); 11 | } 12 | 13 | // Headings for larger alerts 14 | .alert-heading { 15 | // Specified to prevent conflicts of changing $headings-color 16 | color: inherit; 17 | } 18 | 19 | // Provide class for links that match alerts 20 | .alert-link { 21 | font-weight: $alert-link-font-weight; 22 | } 23 | 24 | 25 | // Dismissible alerts 26 | // 27 | // Expand the right padding and account for the close button's positioning. 28 | 29 | .alert-dismissible { 30 | padding-right: $alert-dismissible-padding-r; 31 | 32 | // Adjust close link position 33 | .btn-close { 34 | position: absolute; 35 | top: 0; 36 | right: 0; 37 | z-index: $stretched-link-z-index + 1; 38 | padding: $alert-padding-y * 1.25 $alert-padding-x; 39 | } 40 | } 41 | 42 | 43 | // scss-docs-start alert-modifiers 44 | // Generate contextual modifier classes for colorizing the alert. 45 | 46 | @each $state, $value in $theme-colors { 47 | $alert-background: shift-color($value, $alert-bg-scale); 48 | $alert-border: shift-color($value, $alert-border-scale); 49 | $alert-color: shift-color($value, $alert-color-scale); 50 | @if (contrast-ratio($alert-background, $alert-color) < $min-contrast-ratio) { 51 | $alert-color: mix($value, color-contrast($alert-background), abs($alert-color-scale)); 52 | } 53 | .alert-#{$state} { 54 | @include alert-variant($alert-background, $alert-border, $alert-color); 55 | } 56 | } 57 | // scss-docs-end alert-modifiers 58 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_badge.scss: -------------------------------------------------------------------------------- 1 | // Base class 2 | // 3 | // Requires one of the contextual, color modifier classes for `color` and 4 | // `background-color`. 5 | 6 | .badge { 7 | display: inline-block; 8 | padding: $badge-padding-y $badge-padding-x; 9 | @include font-size($badge-font-size); 10 | font-weight: $badge-font-weight; 11 | line-height: 1; 12 | color: $badge-color; 13 | text-align: center; 14 | white-space: nowrap; 15 | vertical-align: baseline; 16 | @include border-radius($badge-border-radius); 17 | @include gradient-bg(); 18 | 19 | // Empty badges collapse automatically 20 | &:empty { 21 | display: none; 22 | } 23 | } 24 | 25 | // Quick fix for badges in buttons 26 | .btn .badge { 27 | position: relative; 28 | top: -1px; 29 | } 30 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_breadcrumb.scss: -------------------------------------------------------------------------------- 1 | .breadcrumb { 2 | display: flex; 3 | flex-wrap: wrap; 4 | padding: $breadcrumb-padding-y $breadcrumb-padding-x; 5 | margin-bottom: $breadcrumb-margin-bottom; 6 | @include font-size($breadcrumb-font-size); 7 | list-style: none; 8 | background-color: $breadcrumb-bg; 9 | @include border-radius($breadcrumb-border-radius); 10 | } 11 | 12 | .breadcrumb-item { 13 | // The separator between breadcrumbs (by default, a forward-slash: "/") 14 | + .breadcrumb-item { 15 | padding-left: $breadcrumb-item-padding-x; 16 | 17 | &::before { 18 | float: left; // Suppress inline spacings and underlining of the separator 19 | padding-right: $breadcrumb-item-padding-x; 20 | color: $breadcrumb-divider-color; 21 | content: var(--#{$variable-prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$variable-prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"}; 22 | } 23 | } 24 | 25 | &.active { 26 | color: $breadcrumb-active-color; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_button-group.scss: -------------------------------------------------------------------------------- 1 | // Make the div behave like a button 2 | .btn-group, 3 | .btn-group-vertical { 4 | position: relative; 5 | display: inline-flex; 6 | vertical-align: middle; // match .btn alignment given font-size hack above 7 | 8 | > .btn { 9 | position: relative; 10 | flex: 1 1 auto; 11 | } 12 | 13 | // Bring the hover, focused, and "active" buttons to the front to overlay 14 | // the borders properly 15 | > .btn-check:checked + .btn, 16 | > .btn-check:focus + .btn, 17 | > .btn:hover, 18 | > .btn:focus, 19 | > .btn:active, 20 | > .btn.active { 21 | z-index: 1; 22 | } 23 | } 24 | 25 | // Optional: Group multiple button groups together for a toolbar 26 | .btn-toolbar { 27 | display: flex; 28 | flex-wrap: wrap; 29 | justify-content: flex-start; 30 | 31 | .input-group { 32 | width: auto; 33 | } 34 | } 35 | 36 | .btn-group { 37 | // Prevent double borders when buttons are next to each other 38 | > .btn:not(:first-child), 39 | > .btn-group:not(:first-child) { 40 | margin-left: -$btn-border-width; 41 | } 42 | 43 | // Reset rounded corners 44 | > .btn:not(:last-child):not(.dropdown-toggle), 45 | > .btn-group:not(:last-child) > .btn { 46 | @include border-end-radius(0); 47 | } 48 | 49 | // The left radius should be 0 if the button is: 50 | // - the "third or more" child 51 | // - the second child and the previous element isn't `.btn-check` (making it the first child visually) 52 | // - part of a btn-group which isn't the first child 53 | > .btn:nth-child(n + 3), 54 | > :not(.btn-check) + .btn, 55 | > .btn-group:not(:first-child) > .btn { 56 | @include border-start-radius(0); 57 | } 58 | } 59 | 60 | // Sizing 61 | // 62 | // Remix the default button sizing classes into new ones for easier manipulation. 63 | 64 | .btn-group-sm > .btn { @extend .btn-sm; } 65 | .btn-group-lg > .btn { @extend .btn-lg; } 66 | 67 | 68 | // 69 | // Split button dropdowns 70 | // 71 | 72 | .dropdown-toggle-split { 73 | padding-right: $btn-padding-x * .75; 74 | padding-left: $btn-padding-x * .75; 75 | 76 | &::after, 77 | .dropup &::after, 78 | .dropend &::after { 79 | margin-left: 0; 80 | } 81 | 82 | .dropstart &::before { 83 | margin-right: 0; 84 | } 85 | } 86 | 87 | .btn-sm + .dropdown-toggle-split { 88 | padding-right: $btn-padding-x-sm * .75; 89 | padding-left: $btn-padding-x-sm * .75; 90 | } 91 | 92 | .btn-lg + .dropdown-toggle-split { 93 | padding-right: $btn-padding-x-lg * .75; 94 | padding-left: $btn-padding-x-lg * .75; 95 | } 96 | 97 | 98 | // The clickable button for toggling the menu 99 | // Set the same inset shadow as the :active state 100 | .btn-group.show .dropdown-toggle { 101 | @include box-shadow($btn-active-box-shadow); 102 | 103 | // Show no shadow for `.btn-link` since it has no other button styles. 104 | &.btn-link { 105 | @include box-shadow(none); 106 | } 107 | } 108 | 109 | 110 | // 111 | // Vertical button groups 112 | // 113 | 114 | .btn-group-vertical { 115 | flex-direction: column; 116 | align-items: flex-start; 117 | justify-content: center; 118 | 119 | > .btn, 120 | > .btn-group { 121 | width: 100%; 122 | } 123 | 124 | > .btn:not(:first-child), 125 | > .btn-group:not(:first-child) { 126 | margin-top: -$btn-border-width; 127 | } 128 | 129 | // Reset rounded corners 130 | > .btn:not(:last-child):not(.dropdown-toggle), 131 | > .btn-group:not(:last-child) > .btn { 132 | @include border-bottom-radius(0); 133 | } 134 | 135 | > .btn ~ .btn, 136 | > .btn-group:not(:first-child) > .btn { 137 | @include border-top-radius(0); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_buttons.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Base styles 3 | // 4 | 5 | .btn { 6 | display: inline-block; 7 | font-family: $btn-font-family; 8 | font-weight: $btn-font-weight; 9 | line-height: $btn-line-height; 10 | color: $body-color; 11 | text-align: center; 12 | text-decoration: if($link-decoration == none, null, none); 13 | white-space: $btn-white-space; 14 | vertical-align: middle; 15 | cursor: if($enable-button-pointers, pointer, null); 16 | user-select: none; 17 | background-color: transparent; 18 | border: $btn-border-width solid transparent; 19 | @include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-border-radius); 20 | @include transition($btn-transition); 21 | 22 | &:hover { 23 | color: $body-color; 24 | text-decoration: if($link-hover-decoration == underline, none, null); 25 | } 26 | 27 | .btn-check:focus + &, 28 | &:focus { 29 | outline: 0; 30 | box-shadow: $btn-focus-box-shadow; 31 | } 32 | 33 | .btn-check:checked + &, 34 | .btn-check:active + &, 35 | &:active, 36 | &.active { 37 | @include box-shadow($btn-active-box-shadow); 38 | 39 | &:focus { 40 | @include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow); 41 | } 42 | } 43 | 44 | &:disabled, 45 | &.disabled, 46 | fieldset:disabled & { 47 | pointer-events: none; 48 | opacity: $btn-disabled-opacity; 49 | @include box-shadow(none); 50 | } 51 | } 52 | 53 | 54 | // 55 | // Alternate buttons 56 | // 57 | 58 | // scss-docs-start btn-variant-loops 59 | @each $color, $value in $theme-colors { 60 | .btn-#{$color} { 61 | @include button-variant($value, $value); 62 | } 63 | } 64 | 65 | @each $color, $value in $theme-colors { 66 | .btn-outline-#{$color} { 67 | @include button-outline-variant($value); 68 | } 69 | } 70 | // scss-docs-end btn-variant-loops 71 | 72 | 73 | // 74 | // Link buttons 75 | // 76 | 77 | // Make a button look and behave like a link 78 | .btn-link { 79 | font-weight: $font-weight-normal; 80 | color: $btn-link-color; 81 | text-decoration: $link-decoration; 82 | 83 | &:hover { 84 | color: $btn-link-hover-color; 85 | text-decoration: $link-hover-decoration; 86 | } 87 | 88 | &:focus { 89 | text-decoration: $link-hover-decoration; 90 | } 91 | 92 | &:disabled, 93 | &.disabled { 94 | color: $btn-link-disabled-color; 95 | } 96 | 97 | // No need for an active state here 98 | } 99 | 100 | 101 | // 102 | // Button Sizes 103 | // 104 | 105 | .btn-lg { 106 | @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg); 107 | } 108 | 109 | .btn-sm { 110 | @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm); 111 | } 112 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_card.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Base styles 3 | // 4 | 5 | .card { 6 | position: relative; 7 | display: flex; 8 | flex-direction: column; 9 | min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106 10 | height: $card-height; 11 | word-wrap: break-word; 12 | background-color: $card-bg; 13 | background-clip: border-box; 14 | border: $card-border-width solid $card-border-color; 15 | @include border-radius($card-border-radius); 16 | @include box-shadow($card-box-shadow); 17 | 18 | > hr { 19 | margin-right: 0; 20 | margin-left: 0; 21 | } 22 | 23 | > .list-group { 24 | border-top: inherit; 25 | border-bottom: inherit; 26 | 27 | &:first-child { 28 | border-top-width: 0; 29 | @include border-top-radius($card-inner-border-radius); 30 | } 31 | 32 | &:last-child { 33 | border-bottom-width: 0; 34 | @include border-bottom-radius($card-inner-border-radius); 35 | } 36 | } 37 | 38 | // Due to specificity of the above selector (`.card > .list-group`), we must 39 | // use a child selector here to prevent double borders. 40 | > .card-header + .list-group, 41 | > .list-group + .card-footer { 42 | border-top: 0; 43 | } 44 | } 45 | 46 | .card-body { 47 | // Enable `flex-grow: 1` for decks and groups so that card blocks take up 48 | // as much space as possible, ensuring footers are aligned to the bottom. 49 | flex: 1 1 auto; 50 | padding: $card-spacer-y $card-spacer-x; 51 | color: $card-color; 52 | } 53 | 54 | .card-title { 55 | margin-bottom: $card-title-spacer-y; 56 | } 57 | 58 | .card-subtitle { 59 | margin-top: -$card-title-spacer-y * .5; 60 | margin-bottom: 0; 61 | } 62 | 63 | .card-text:last-child { 64 | margin-bottom: 0; 65 | } 66 | 67 | .card-link { 68 | &:hover { 69 | text-decoration: if($link-hover-decoration == underline, none, null); 70 | } 71 | 72 | + .card-link { 73 | margin-left: $card-spacer-x; 74 | } 75 | } 76 | 77 | // 78 | // Optional textual caps 79 | // 80 | 81 | .card-header { 82 | padding: $card-cap-padding-y $card-cap-padding-x; 83 | margin-bottom: 0; // Removes the default margin-bottom of 84 | color: $card-cap-color; 85 | background-color: $card-cap-bg; 86 | border-bottom: $card-border-width solid $card-border-color; 87 | 88 | &:first-child { 89 | @include border-radius($card-inner-border-radius $card-inner-border-radius 0 0); 90 | } 91 | } 92 | 93 | .card-footer { 94 | padding: $card-cap-padding-y $card-cap-padding-x; 95 | color: $card-cap-color; 96 | background-color: $card-cap-bg; 97 | border-top: $card-border-width solid $card-border-color; 98 | 99 | &:last-child { 100 | @include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius); 101 | } 102 | } 103 | 104 | 105 | // 106 | // Header navs 107 | // 108 | 109 | .card-header-tabs { 110 | margin-right: -$card-cap-padding-x * .5; 111 | margin-bottom: -$card-cap-padding-y; 112 | margin-left: -$card-cap-padding-x * .5; 113 | border-bottom: 0; 114 | 115 | @if $nav-tabs-link-active-bg != $card-bg { 116 | .nav-link.active { 117 | background-color: $card-bg; 118 | border-bottom-color: $card-bg; 119 | } 120 | } 121 | } 122 | 123 | .card-header-pills { 124 | margin-right: -$card-cap-padding-x * .5; 125 | margin-left: -$card-cap-padding-x * .5; 126 | } 127 | 128 | // Card image 129 | .card-img-overlay { 130 | position: absolute; 131 | top: 0; 132 | right: 0; 133 | bottom: 0; 134 | left: 0; 135 | padding: $card-img-overlay-padding; 136 | @include border-radius($card-inner-border-radius); 137 | } 138 | 139 | .card-img, 140 | .card-img-top, 141 | .card-img-bottom { 142 | width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch 143 | } 144 | 145 | .card-img, 146 | .card-img-top { 147 | @include border-top-radius($card-inner-border-radius); 148 | } 149 | 150 | .card-img, 151 | .card-img-bottom { 152 | @include border-bottom-radius($card-inner-border-radius); 153 | } 154 | 155 | 156 | // 157 | // Card groups 158 | // 159 | 160 | .card-group { 161 | // The child selector allows nested `.card` within `.card-group` 162 | // to display properly. 163 | > .card { 164 | margin-bottom: $card-group-margin; 165 | } 166 | 167 | @include media-breakpoint-up(sm) { 168 | display: flex; 169 | flex-flow: row wrap; 170 | // The child selector allows nested `.card` within `.card-group` 171 | // to display properly. 172 | > .card { 173 | // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 174 | flex: 1 0 0%; 175 | margin-bottom: 0; 176 | 177 | + .card { 178 | margin-left: 0; 179 | border-left: 0; 180 | } 181 | 182 | // Handle rounded corners 183 | @if $enable-rounded { 184 | &:not(:last-child) { 185 | @include border-end-radius(0); 186 | 187 | .card-img-top, 188 | .card-header { 189 | // stylelint-disable-next-line property-disallowed-list 190 | border-top-right-radius: 0; 191 | } 192 | .card-img-bottom, 193 | .card-footer { 194 | // stylelint-disable-next-line property-disallowed-list 195 | border-bottom-right-radius: 0; 196 | } 197 | } 198 | 199 | &:not(:first-child) { 200 | @include border-start-radius(0); 201 | 202 | .card-img-top, 203 | .card-header { 204 | // stylelint-disable-next-line property-disallowed-list 205 | border-top-left-radius: 0; 206 | } 207 | .card-img-bottom, 208 | .card-footer { 209 | // stylelint-disable-next-line property-disallowed-list 210 | border-bottom-left-radius: 0; 211 | } 212 | } 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_close.scss: -------------------------------------------------------------------------------- 1 | // transparent background and border properties included for button version. 2 | // iOS requires the button element instead of an anchor tag. 3 | // If you want the anchor version, it requires `href="#"`. 4 | // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile 5 | 6 | .btn-close { 7 | box-sizing: content-box; 8 | width: $btn-close-width; 9 | height: $btn-close-height; 10 | padding: $btn-close-padding-y $btn-close-padding-x; 11 | color: $btn-close-color; 12 | background: transparent escape-svg($btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements 13 | border: 0; // for button elements 14 | @include border-radius(); 15 | opacity: $btn-close-opacity; 16 | 17 | // Override 's hover style 18 | &:hover { 19 | color: $btn-close-color; 20 | text-decoration: none; 21 | opacity: $btn-close-hover-opacity; 22 | } 23 | 24 | &:focus { 25 | outline: 0; 26 | box-shadow: $btn-close-focus-shadow; 27 | opacity: $btn-close-focus-opacity; 28 | } 29 | 30 | &:disabled, 31 | &.disabled { 32 | pointer-events: none; 33 | user-select: none; 34 | opacity: $btn-close-disabled-opacity; 35 | } 36 | } 37 | 38 | .btn-close-white { 39 | filter: $btn-close-white-filter; 40 | } 41 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_containers.scss: -------------------------------------------------------------------------------- 1 | // Container widths 2 | // 3 | // Set the container width, and override it for fixed navbars in media queries. 4 | 5 | @if $enable-grid-classes { 6 | // Single container class with breakpoint max-widths 7 | .container, 8 | // 100% wide container at all breakpoints 9 | .container-fluid { 10 | @include make-container(); 11 | } 12 | 13 | // Responsive containers that are 100% wide until a breakpoint 14 | @each $breakpoint, $container-max-width in $container-max-widths { 15 | .container-#{$breakpoint} { 16 | @extend .container-fluid; 17 | } 18 | 19 | @include media-breakpoint-up($breakpoint, $grid-breakpoints) { 20 | %responsive-container-#{$breakpoint} { 21 | max-width: $container-max-width; 22 | } 23 | 24 | // Extend each breakpoint which is smaller or equal to the current breakpoint 25 | $extend-breakpoint: true; 26 | 27 | @each $name, $width in $grid-breakpoints { 28 | @if ($extend-breakpoint) { 29 | .container#{breakpoint-infix($name, $grid-breakpoints)} { 30 | @extend %responsive-container-#{$breakpoint}; 31 | } 32 | 33 | // Once the current breakpoint is reached, stop extending 34 | @if ($breakpoint == $name) { 35 | $extend-breakpoint: false; 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_forms.scss: -------------------------------------------------------------------------------- 1 | @import "forms/labels"; 2 | @import "forms/form-text"; 3 | @import "forms/form-control"; 4 | @import "forms/form-select"; 5 | @import "forms/form-check"; 6 | @import "forms/form-range"; 7 | @import "forms/floating-labels"; 8 | @import "forms/input-group"; 9 | @import "forms/validation"; 10 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_grid.scss: -------------------------------------------------------------------------------- 1 | // Row 2 | // 3 | // Rows contain your columns. 4 | 5 | @if $enable-grid-classes { 6 | .row { 7 | @include make-row(); 8 | 9 | > * { 10 | @include make-col-ready(); 11 | } 12 | } 13 | } 14 | 15 | @if $enable-cssgrid { 16 | .grid { 17 | display: grid; 18 | grid-template-rows: repeat(var(--#{$variable-prefix}rows, 1), 1fr); 19 | grid-template-columns: repeat(var(--#{$variable-prefix}columns, #{$grid-columns}), 1fr); 20 | gap: var(--#{$variable-prefix}gap, #{$grid-gutter-width}); 21 | 22 | @include make-cssgrid(); 23 | } 24 | } 25 | 26 | 27 | // Columns 28 | // 29 | // Common styles for small and large grid columns 30 | 31 | @if $enable-grid-classes { 32 | @include make-grid-columns(); 33 | } 34 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_helpers.scss: -------------------------------------------------------------------------------- 1 | @import "helpers/clearfix"; 2 | @import "helpers/colored-links"; 3 | @import "helpers/ratio"; 4 | @import "helpers/position"; 5 | @import "helpers/stacks"; 6 | @import "helpers/visually-hidden"; 7 | @import "helpers/stretched-link"; 8 | @import "helpers/text-truncation"; 9 | @import "helpers/vr"; 10 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_images.scss: -------------------------------------------------------------------------------- 1 | // Responsive images (ensure images don't scale beyond their parents) 2 | // 3 | // This is purposefully opt-in via an explicit class rather than being the default for all ``s. 4 | // We previously tried the "images are responsive by default" approach in Bootstrap v2, 5 | // and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps) 6 | // which weren't expecting the images within themselves to be involuntarily resized. 7 | // See also https://github.com/twbs/bootstrap/issues/18178 8 | .img-fluid { 9 | @include img-fluid(); 10 | } 11 | 12 | 13 | // Image thumbnails 14 | .img-thumbnail { 15 | padding: $thumbnail-padding; 16 | background-color: $thumbnail-bg; 17 | border: $thumbnail-border-width solid $thumbnail-border-color; 18 | @include border-radius($thumbnail-border-radius); 19 | @include box-shadow($thumbnail-box-shadow); 20 | 21 | // Keep them at most 100% wide 22 | @include img-fluid(); 23 | } 24 | 25 | // 26 | // Figures 27 | // 28 | 29 | .figure { 30 | // Ensures the caption's text aligns with the image. 31 | display: inline-block; 32 | } 33 | 34 | .figure-img { 35 | margin-bottom: $spacer * .5; 36 | line-height: 1; 37 | } 38 | 39 | .figure-caption { 40 | @include font-size($figure-caption-font-size); 41 | color: $figure-caption-color; 42 | } 43 | -------------------------------------------------------------------------------- /Core/wwwroot/lib/bootstrap/scss/_list-group.scss: -------------------------------------------------------------------------------- 1 | // Base class 2 | // 3 | // Easily usable on