internalEmployeeViewModels)
9 | {
10 | InternalEmployees = internalEmployeeViewModels.ToList();
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/ViewModels/ErrorViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.ViewModels
2 | {
3 | public class ErrorViewModel
4 | {
5 | public string? RequestId { get; set; }
6 |
7 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
8 | }
9 | }
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/ViewModels/InternalEmployeeDetailViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.ViewModels
2 | {
3 | public class InternalEmployeeDetailViewModel
4 | {
5 | public Guid Id { get; set; }
6 |
7 | public string FirstName { get; set; } = string.Empty;
8 |
9 | public string LastName { get; set; } = string.Empty;
10 |
11 | public string FullName
12 | {
13 | get { return $"{FirstName} {LastName}"; }
14 | }
15 |
16 | public int YearsInService { get; set; }
17 |
18 | public decimal SuggestedBonus { get; set; }
19 |
20 | public decimal Salary { get; set; }
21 |
22 | public bool MinimumRaiseGiven { get; set; }
23 |
24 | public int JobLevel { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/ViewModels/InternalEmployeeForOverviewViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace EmployeeManagement.ViewModels
4 | {
5 | public class InternalEmployeeForOverviewViewModel
6 | {
7 | public Guid Id { get; set; }
8 |
9 | public string FirstName { get; set; } = string.Empty;
10 |
11 | public string LastName { get; set; } = string.Empty;
12 |
13 | public string FullName
14 | {
15 | get { return $"{FirstName} {LastName}"; }
16 | }
17 |
18 | public int YearsInService { get; set; }
19 |
20 | public decimal SuggestedBonus { get; set; }
21 |
22 | public decimal Salary { get; set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/ViewModels/StatisticsViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.ViewModels
2 | {
3 | public class StatisticsViewModel
4 | {
5 | public string LocalIpAddress { get; set; } = string.Empty;
6 | public int LocalPort { get; set; }
7 | public string RemoteIpAddress { get; set; } = string.Empty;
8 | public int RemotePort { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/EmployeeOverview/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model EmployeeManagement.ViewModels.EmployeeOverviewViewModel
2 | @{
3 | ViewData["Title"] = "Employee Management";
4 | }
5 |
6 |
7 | Employee Management
8 |
9 |
10 | Add new internal employee
11 |
12 |
13 |
14 |
15 |
16 | @Html.DisplayNameFor(model => model.InternalEmployees[0].FullName)
17 | |
18 |
19 | @Html.DisplayNameFor(model => model.InternalEmployees[0].Salary)
20 | |
21 |
22 | @Html.DisplayNameFor(model => model.InternalEmployees[0].YearsInService)
23 | |
24 |
25 | @Html.DisplayNameFor(model => model.InternalEmployees[0].SuggestedBonus)
26 | |
27 |
28 |
29 |
30 | @foreach (var item in Model.InternalEmployees)
31 | {
32 |
33 |
34 | @Html.DisplayFor(modelItem => item.FullName)
35 | |
36 |
37 | @Html.DisplayFor(modelItem => item.Salary)
38 | |
39 |
40 | @Html.DisplayFor(modelItem => item.YearsInService)
41 | |
42 |
43 | @Html.DisplayFor(modelItem => item.SuggestedBonus)
44 | |
45 |
46 | Details
48 | |
49 |
50 | }
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/InternalEmployee/AddInternalEmployee.cshtml:
--------------------------------------------------------------------------------
1 | @model EmployeeManagement.ViewModels.CreateInternalEmployeeViewModel
2 | @{
3 | ViewData["Title"] = "Add Internal Employee";
4 | }
5 |
6 | Add Internal Employee
7 |
8 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/InternalEmployee/InternalEmployeeDetails.cshtml:
--------------------------------------------------------------------------------
1 | @model EmployeeManagement.ViewModels.InternalEmployeeDetailViewModel
2 | @{
3 | ViewData["Title"] = "Internal Employee Details";
4 | }
5 |
6 |
7 |
Name:
8 |
@Model?.FullName
9 |
10 |
11 |
Salary:
12 |
@Model?.Salary
13 |
14 |
15 |
Years in service:
16 |
@Model?.YearsInService
17 |
18 |
19 |
Suggested bonus:
20 |
@Model?.SuggestedBonus
21 |
22 |
23 |
Current job level:
24 |
@Model?.JobLevel
25 |
26 |
27 |
33 |
34 |
35 |
@ViewBag.PromotionRequestMessage
36 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model ErrorViewModel
2 | @{
3 | ViewData["Title"] = "Error";
4 | }
5 |
6 | Error.
7 | An error occurred while processing your request.
8 |
9 | @if (Model?.ShowRequestId ?? false)
10 | {
11 |
12 | Request ID: @Model?.RequestId
13 |
14 | }
15 |
16 | Development Mode
17 |
18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | The Development environment shouldn't be enabled for deployed applications.
22 | It can result in displaying sensitive information from exceptions to end users.
23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
24 | and restarting the app.
25 |
26 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @ViewData["Title"] - Employee Management
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
32 | @RenderBody()
33 |
34 |
35 |
36 |
38 |
39 |
40 |
41 | @await RenderSectionAsync("Scripts", required: false)
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/Shared/_Layout.cshtml.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | for details on configuring this project to bundle and minify static web assets. */
3 |
4 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | a {
11 | color: #0077cc;
12 | }
13 |
14 | .btn-primary {
15 | color: #fff;
16 | background-color: #1b6ec2;
17 | border-color: #1861ac;
18 | }
19 |
20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
21 | color: #fff;
22 | background-color: #1b6ec2;
23 | border-color: #1861ac;
24 | }
25 |
26 | .border-top {
27 | border-top: 1px solid #e5e5e5;
28 | }
29 | .border-bottom {
30 | border-bottom: 1px solid #e5e5e5;
31 | }
32 |
33 | .box-shadow {
34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
35 | }
36 |
37 | button.accept-policy {
38 | font-size: 1rem;
39 | line-height: inherit;
40 | }
41 |
42 | .footer {
43 | position: absolute;
44 | bottom: 0;
45 | width: 100%;
46 | white-space: nowrap;
47 | line-height: 60px;
48 | }
49 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/Statistics/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model EmployeeManagement.ViewModels.StatisticsViewModel
2 | @{
3 | ViewData["Title"] = "Statistics";
4 | }
5 |
6 |
7 | Statistics
8 |
9 | Local address: @Model?.LocalIpAddress, port @Model?.LocalPort
10 | Remote address: @Model?.RemoteIpAddress, port @Model?.RemotePort
11 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using EmployeeManagement
2 | @using EmployeeManagement.ViewModels
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "ConnectionStrings": {
10 | "EmployeeManagementDB": "Data Source=EmployeeManagement.db"
11 | //"EmployeeManagementDB": "Data Source=:memory:"
12 | },
13 | "TopLevelManagementAPIRoot": "http://localhost:5057"
14 | }
15 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 14px;
3 | }
4 |
5 | @media (min-width: 768px) {
6 | html {
7 | font-size: 16px;
8 | }
9 | }
10 |
11 | html {
12 | position: relative;
13 | min-height: 100%;
14 | }
15 |
16 | body {
17 | margin-bottom: 60px;
18 | }
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinDockx/UnitTestingAspNetCoreMVC/37d75208496b50ca2cfc0b07ec8ffb21dd8294e4/Finished solution/EmployeeManagement/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your JavaScript code.
5 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2021 Twitter, Inc.
4 | Copyright (c) 2011-2021 The Bootstrap Authors
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 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) .NET Foundation. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js:
--------------------------------------------------------------------------------
1 | // Unobtrusive validation support library for jQuery and jQuery Validate
2 | // Copyright (c) .NET Foundation. All rights reserved.
3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
4 | // @version v3.2.11
5 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery-validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(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 u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=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),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("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 m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(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(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.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)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.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)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.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)}),m.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)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive});
6 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/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 |
--------------------------------------------------------------------------------
/Finished solution/EmployeeManagement/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors, https://js.foundation/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | All files located in the node_modules and external directories are
34 | externally maintained libraries used by this software which have their
35 | own licenses; we recommend you read them, as their terms may differ from
36 | the terms above.
37 |
--------------------------------------------------------------------------------
/Finished solution/TopLevelManagement/Controllers/PromotionEligibilitiesController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace TopLevelManagement.Controllers
4 | {
5 | [ApiController]
6 | [Route("api/promotioneligibilities")]
7 | public class PromotionEligibilitiesController : ControllerBase
8 | {
9 | [HttpGet("{employeeId}")]
10 | public IActionResult EmployeeIsEligibleForPromotion(Guid employeeId)
11 | {
12 | // For demo purposes, Megan (id = 72f2f5fe-e50c-4966-8420-d50258aefdcb)
13 | // is eligible for promotion, other employees aren't
14 | if (employeeId == Guid.Parse("72f2f5fe-e50c-4966-8420-d50258aefdcb"))
15 | {
16 | return Ok(new { EligibleForPromotion = true });
17 | }
18 |
19 | return Ok(new { EligibleForPromotion = false });
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/Finished solution/TopLevelManagement/Program.cs:
--------------------------------------------------------------------------------
1 | var builder = WebApplication.CreateBuilder(args);
2 |
3 | // Add services to the container.
4 |
5 | builder.Services.AddControllers();
6 |
7 | var app = builder.Build();
8 |
9 | // Configure the HTTP request pipeline.
10 |
11 | app.UseAuthorization();
12 |
13 | app.MapControllers();
14 |
15 | app.Run();
16 |
--------------------------------------------------------------------------------
/Finished solution/TopLevelManagement/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:13433",
8 | "sslPort": 0
9 | }
10 | },
11 | "profiles": {
12 | "TopLevelManagement.API": {
13 | "commandName": "Project",
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | },
17 | "applicationUrl": "http://localhost:5057",
18 | "dotnetRunMessages": true
19 | },
20 | "IIS Express": {
21 | "commandName": "IISExpress",
22 | "launchBrowser": true,
23 | "launchUrl": "weatherforecast",
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development"
26 | }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/Finished solution/TopLevelManagement/TopLevelManagement.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Finished solution/TopLevelManagement/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Finished solution/TopLevelManagement/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Kevin Dockx
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Unit Testing an ASP.NET Core MVC Web Application
2 | Fully functioning sample code for my Unit Testing an ASP.NET Core MVC Web Application course, over at Pluralsight, currently targeting .NET 8.
3 |
4 | The **main** branch exactly matches the course.
5 | The **latest-and-greatest** branch contains changes that were incorporated after recording. Most often these changes are language features that are relativley new and/or in preview, like primary constructors, switch expressions and so on. Most of these changes will probably make it into the main branch when course updates happen, but if you don't want to wait for that you can already check it out - enjoy :-)
6 |
7 | All my courses can be found at https://app.pluralsight.com/profile/author/kevin-dockx
8 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EmployeeManagement", "EmployeeManagement\EmployeeManagement.csproj", "{A9D381CA-BB06-4106-AA5F-CD2FC0D436F6}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TopLevelManagement", "TopLevelManagement\TopLevelManagement.csproj", "{6807FF4E-072D-4AE3-81BC-67D75FE791DC}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {A9D381CA-BB06-4106-AA5F-CD2FC0D436F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {A9D381CA-BB06-4106-AA5F-CD2FC0D436F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {A9D381CA-BB06-4106-AA5F-CD2FC0D436F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {A9D381CA-BB06-4106-AA5F-CD2FC0D436F6}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {6807FF4E-072D-4AE3-81BC-67D75FE791DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {6807FF4E-072D-4AE3-81BC-67D75FE791DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {6807FF4E-072D-4AE3-81BC-67D75FE791DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {6807FF4E-072D-4AE3-81BC-67D75FE791DC}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {4771D570-E205-4450-92AC-5F5F9D2C9729}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/ActionFilters/CheckShowStatisticsHeader.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.AspNetCore.Mvc.Filters;
3 |
4 | namespace EmployeeManagement.ActionFilters
5 | {
6 | public class CheckShowStatisticsHeader : ActionFilterAttribute
7 | {
8 | public override void OnActionExecuting(ActionExecutingContext context)
9 | {
10 | // if the ShowStatistics header is missing or set to false,
11 | // a BadRequest must be returned.
12 | if (!context.HttpContext.Request.Headers.ContainsKey("ShowStatistics"))
13 | {
14 | context.Result = new BadRequestResult();
15 | }
16 |
17 | // get the ShowStatistics header
18 | if (!bool.TryParse(
19 | context.HttpContext.Request.Headers["ShowStatistics"].ToString(),
20 | out bool showStatisticsValue))
21 | {
22 | context.Result = new BadRequestResult();
23 | }
24 |
25 | // check the value
26 | if (!showStatisticsValue)
27 | {
28 | context.Result = new BadRequestResult();
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Business/EmployeeFactory.cs:
--------------------------------------------------------------------------------
1 | using EmployeeManagement.DataAccess.Entities;
2 |
3 | namespace EmployeeManagement.Business
4 | {
5 | ///
6 | /// Factory for creation employees
7 | ///
8 | public class EmployeeFactory
9 | {
10 | ///
11 | /// Create an employee
12 | ///
13 | public virtual Employee CreateEmployee(string firstName,
14 | string lastName,
15 | string? company = null,
16 | bool isExternal = false)
17 | {
18 | if (string.IsNullOrEmpty(firstName))
19 | {
20 | throw new ArgumentException($"'{nameof(firstName)}' cannot be null or empty.",
21 | nameof(firstName));
22 | }
23 |
24 | if (string.IsNullOrEmpty(lastName))
25 | {
26 | throw new ArgumentException($"'{nameof(lastName)}' cannot be null or empty.",
27 | nameof(lastName));
28 | }
29 |
30 | if (company == null && isExternal)
31 | {
32 | throw new ArgumentException($"'{nameof(company)}' cannot be null or empty when the employee is external.",
33 | nameof(company));
34 | }
35 |
36 | if (isExternal)
37 | {
38 | // we know company won't be null here due to the check above, so
39 | // we can use the null-forgiving operator to notify the compiler of this
40 | return new ExternalEmployee(firstName, lastName, company = null!);
41 | }
42 |
43 | // create a new employee with default values
44 | return new InternalEmployee(firstName, lastName, 0, 2500, false, 1);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Business/EventArguments/EmployeeIsAbsentEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.Business.EventArguments
2 | {
3 | public class EmployeeIsAbsentEventArgs : EventArgs
4 | {
5 | public Guid EmployeeId { get; private set; }
6 |
7 | public EmployeeIsAbsentEventArgs(Guid employeeId)
8 | {
9 | EmployeeId = employeeId;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Business/Exceptions/EmployeeInvalidRaiseException.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.Business.Exceptions
2 | {
3 | public class EmployeeInvalidRaiseException : Exception
4 | {
5 | public int InvalidRaise { get; private set; }
6 | public EmployeeInvalidRaiseException(string message, int raise):
7 | base(message)
8 | {
9 | InvalidRaise = raise;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Business/IEmployeeService.cs:
--------------------------------------------------------------------------------
1 | using EmployeeManagement.Business.EventArguments;
2 | using EmployeeManagement.DataAccess.Entities;
3 |
4 | namespace EmployeeManagement.Business
5 | {
6 | public interface IEmployeeService
7 | {
8 | event EventHandler? EmployeeIsAbsent;
9 | Task AddInternalEmployeeAsync(InternalEmployee internalEmployee);
10 | Task AttendCourseAsync(InternalEmployee employee, Course attendedCourse);
11 | ExternalEmployee CreateExternalEmployee(string firstName, string lastName, string company);
12 | InternalEmployee CreateInternalEmployee(string firstName, string lastName);
13 | Task CreateInternalEmployeeAsync(string firstName, string lastName);
14 | InternalEmployee? FetchInternalEmployee(Guid employeeId);
15 | Task FetchInternalEmployeeAsync(Guid employeeId);
16 | Task> FetchInternalEmployeesAsync();
17 | Task GiveMinimumRaiseAsync(InternalEmployee employee);
18 | Task GiveRaiseAsync(InternalEmployee employee, int raise);
19 | void NotifyOfAbsence(Employee employee);
20 | }
21 | }
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Business/IPromotionService.cs:
--------------------------------------------------------------------------------
1 | using EmployeeManagement.DataAccess.Entities;
2 |
3 | namespace EmployeeManagement.Business
4 | {
5 | public interface IPromotionService
6 | {
7 | Task PromoteInternalEmployeeAsync(InternalEmployee employee);
8 | }
9 | }
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Business/PromotionEligibility.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.Business
2 | {
3 | public class PromotionEligibility
4 | {
5 | public bool EligibleForPromotion { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Business/PromotionService.cs:
--------------------------------------------------------------------------------
1 | using EmployeeManagement.DataAccess.Entities;
2 | using EmployeeManagement.DataAccess.Services;
3 | using System.Net.Http.Headers;
4 | using System.Text.Json;
5 |
6 | namespace EmployeeManagement.Business
7 | {
8 | public class PromotionService : IPromotionService
9 | {
10 | private readonly HttpClient _httpClient;
11 | private readonly IEmployeeManagementRepository _employeeManagementRepository;
12 |
13 | public PromotionService(
14 | HttpClient httpClient,
15 | IEmployeeManagementRepository employeeManagementRepository)
16 | {
17 | _httpClient = httpClient;
18 | _employeeManagementRepository = employeeManagementRepository;
19 | }
20 |
21 | ///
22 | /// Promote an internal employee if eligible for promotion
23 | ///
24 | ///
25 | ///
26 | public async Task PromoteInternalEmployeeAsync(InternalEmployee employee)
27 | {
28 | if (await CheckIfInternalEmployeeIsEligibleForPromotion(employee.Id))
29 | {
30 | employee.JobLevel++;
31 | await _employeeManagementRepository.SaveChangesAsync();
32 | return true;
33 | }
34 | return false;
35 | }
36 |
37 | ///
38 | /// Calls into external API (containing a data source only
39 | /// the top level managers can manage) to check whether
40 | /// an internal employee is eligible for promotion
41 | ///
42 | private async Task CheckIfInternalEmployeeIsEligibleForPromotion(
43 | Guid employeeId)
44 | {
45 | // call into API
46 | var apiRoot = "http://localhost:5057";
47 |
48 | var request = new HttpRequestMessage(HttpMethod.Get,
49 | $"{apiRoot}/api/promotioneligibilities/{employeeId}");
50 | request.Headers.Accept.Add(
51 | new MediaTypeWithQualityHeaderValue("application/json"));
52 |
53 | var response = await _httpClient.SendAsync(request);
54 | response.EnsureSuccessStatusCode();
55 |
56 | // deserialize content
57 | var content = await response.Content.ReadAsStringAsync();
58 | var promotionEligibility = JsonSerializer.Deserialize(content,
59 | new JsonSerializerOptions
60 | {
61 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase
62 | });
63 |
64 | // return value
65 | return promotionEligibility == null ?
66 | false : promotionEligibility.EligibleForPromotion;
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Controllers/EmployeeOverviewController.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using EmployeeManagement.Business;
3 | using EmployeeManagement.ViewModels;
4 | using Microsoft.AspNetCore.Mvc;
5 | using System.Diagnostics;
6 |
7 | namespace EmployeeManagement.Controllers
8 | {
9 | public class EmployeeOverviewController : Controller
10 | {
11 | private readonly IEmployeeService _employeeService;
12 | private readonly IMapper _mapper;
13 |
14 | public EmployeeOverviewController(IEmployeeService employeeService,
15 | IMapper mapper)
16 | {
17 | _employeeService = employeeService;
18 | _mapper = mapper;
19 | }
20 |
21 | public async Task Index()
22 | {
23 | var internalEmployees = await _employeeService.FetchInternalEmployeesAsync();
24 |
25 | // with manual mapping
26 | var internalEmployeeForOverviewViewModels =
27 | internalEmployees.Select(e => new InternalEmployeeForOverviewViewModel()
28 | {
29 | Id = e.Id,
30 | FirstName = e.FirstName,
31 | LastName = e.LastName,
32 | Salary = e.Salary,
33 | SuggestedBonus = e.SuggestedBonus,
34 | YearsInService = e.YearsInService
35 | });
36 |
37 | // with AutoMapper
38 | //var internalEmployeeForOverviewViewModels =
39 | // _mapper.Map>(internalEmployees);
40 |
41 | return View(new EmployeeOverviewViewModel(internalEmployeeForOverviewViewModels));
42 | }
43 |
44 |
45 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
46 | public IActionResult Error()
47 | {
48 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Controllers/InternalEmployeeController.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using EmployeeManagement.Business;
3 | using EmployeeManagement.ViewModels;
4 | using Microsoft.AspNetCore.Mvc;
5 |
6 | namespace EmployeeManagement.Controllers
7 | {
8 | public class InternalEmployeeController : Controller
9 | {
10 | private readonly IEmployeeService _employeeService;
11 | private readonly IMapper _mapper;
12 |
13 | public InternalEmployeeController(IEmployeeService employeeService,
14 | IMapper mapper)
15 | {
16 | _employeeService = employeeService;
17 | _mapper = mapper;
18 | }
19 |
20 | [HttpGet]
21 | public IActionResult AddInternalEmployee()
22 | {
23 | return View(new CreateInternalEmployeeViewModel());
24 | }
25 |
26 | [HttpPost]
27 | public async Task AddInternalEmployee(CreateInternalEmployeeViewModel model)
28 | {
29 | if (!ModelState.IsValid)
30 | {
31 | return BadRequest(ModelState);
32 | }
33 | else
34 | {
35 | // create an internal employee entity with default values filled out
36 | // and the values the user inputted
37 | var internalEmplooyee =
38 | await _employeeService.CreateInternalEmployeeAsync(model.FirstName, model.LastName);
39 |
40 | // persist it
41 | await _employeeService.AddInternalEmployeeAsync(internalEmplooyee);
42 | }
43 |
44 | return RedirectToAction("Index", "EmployeeOverview");
45 | }
46 |
47 | [HttpGet]
48 | public async Task InternalEmployeeDetails(
49 | [FromRoute(Name = "id")] Guid? employeeId)
50 | {
51 | if (!employeeId.HasValue)
52 | {
53 | return RedirectToAction("Index", "EmployeeOverview");
54 | }
55 |
56 | var internalEmployee = await _employeeService.FetchInternalEmployeeAsync(employeeId.Value);
57 | if (internalEmployee == null)
58 | {
59 | return RedirectToAction("Index", "EmployeeOverview");
60 | }
61 |
62 | return View(_mapper.Map(internalEmployee));
63 | }
64 |
65 | [HttpPost]
66 | public async Task ExecutePromotionRequest(
67 | [FromForm(Name = "id")] Guid? employeeId)
68 | {
69 | if (!employeeId.HasValue)
70 | {
71 | return RedirectToAction("Index", "EmployeeOverview");
72 | }
73 |
74 | var internalEmployee = await _employeeService
75 | .FetchInternalEmployeeAsync(employeeId.Value);
76 |
77 | if (internalEmployee == null)
78 | {
79 | return RedirectToAction("Index", "EmployeeOverview");
80 | }
81 |
82 | return View("InternalEmployeeDetails",
83 | _mapper.Map(internalEmployee));
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Controllers/StatisticsController.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using EmployeeManagement.ActionFilters;
3 | using EmployeeManagement.ViewModels;
4 | using Microsoft.AspNetCore.Http.Features;
5 | using Microsoft.AspNetCore.Mvc;
6 |
7 | namespace EmployeeManagement.Controllers
8 | {
9 | public class StatisticsController : Controller
10 | {
11 | private readonly IMapper _mapper;
12 |
13 | public StatisticsController(IMapper mapper)
14 | {
15 | _mapper = mapper;
16 | }
17 |
18 | public IActionResult Index()
19 | {
20 | var httpConnectionFeature = HttpContext.Features.Get();
21 | return View(_mapper.Map(httpConnectionFeature));
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/DataAccess/DbContexts/EmployeeDbContext.cs:
--------------------------------------------------------------------------------
1 | using EmployeeManagement.DataAccess.Entities;
2 | using Microsoft.EntityFrameworkCore;
3 |
4 | namespace EmployeeManagement.DataAccess.DbContexts
5 | {
6 | public class EmployeeDbContext : DbContext
7 | {
8 | public DbSet InternalEmployees { get; set; } = null!;
9 | public DbSet ExternalEmployees { get; set; } = null!;
10 | public DbSet Courses { get; set; } = null!;
11 |
12 | public EmployeeDbContext(DbContextOptions options)
13 | : base(options)
14 | {
15 | }
16 |
17 | protected override void OnModelCreating(ModelBuilder modelBuilder)
18 | {
19 | var obligatoryCourse1 = new Course("Company Introduction")
20 | {
21 | Id = Guid.Parse("37e03ca7-c730-4351-834c-b66f280cdb01"),
22 | IsNew = false
23 | };
24 |
25 | var obligatoryCourse2 = new Course("Respecting Your Colleagues")
26 | {
27 | Id = Guid.Parse("1fd115cf-f44c-4982-86bc-a8fe2e4ff83e"),
28 | IsNew = false
29 | };
30 |
31 | var optionalCourse1 = new Course("Dealing with Customers 101")
32 | {
33 | Id = Guid.Parse("844e14ce-c055-49e9-9610-855669c9859b"),
34 | IsNew = false
35 | };
36 |
37 | modelBuilder.Entity()
38 | .HasData(obligatoryCourse1,
39 | obligatoryCourse2,
40 | optionalCourse1,
41 | new Course("Dealing with Customers - Advanced")
42 | {
43 | Id = Guid.Parse("d6e0e4b7-9365-4332-9b29-bb7bf09664a6"),
44 | IsNew = false
45 | },
46 | new Course("Disaster Management 101")
47 | {
48 | Id = Guid.Parse("cbf6db3b-c4ee-46aa-9457-5fa8aefef33a"),
49 | IsNew = false
50 | }
51 | );
52 |
53 | modelBuilder.Entity()
54 | .HasData(
55 | new InternalEmployee("Megan", "Jones", 2, 3000, false, 2)
56 | {
57 | Id = Guid.Parse("72f2f5fe-e50c-4966-8420-d50258aefdcb")
58 | },
59 | new InternalEmployee("Jaimy", "Johnson", 3, 3400, true, 1)
60 | {
61 | Id = Guid.Parse("f484ad8f-78fd-46d1-9f87-bbb1e676e37f")
62 | });
63 |
64 | modelBuilder
65 | .Entity()
66 | .HasMany(p => p.AttendedCourses)
67 | .WithMany(p => p.EmployeesThatAttended)
68 | .UsingEntity(j => j.ToTable("CourseInternalEmployee").HasData(new[]
69 | {
70 | new { AttendedCoursesId = Guid.Parse("37e03ca7-c730-4351-834c-b66f280cdb01"),
71 | EmployeesThatAttendedId = Guid.Parse("72f2f5fe-e50c-4966-8420-d50258aefdcb") },
72 | new { AttendedCoursesId = Guid.Parse("1fd115cf-f44c-4982-86bc-a8fe2e4ff83e"),
73 | EmployeesThatAttendedId = Guid.Parse("72f2f5fe-e50c-4966-8420-d50258aefdcb") },
74 | new { AttendedCoursesId = Guid.Parse("37e03ca7-c730-4351-834c-b66f280cdb01"),
75 | EmployeesThatAttendedId = Guid.Parse("f484ad8f-78fd-46d1-9f87-bbb1e676e37f") },
76 | new { AttendedCoursesId = Guid.Parse("1fd115cf-f44c-4982-86bc-a8fe2e4ff83e"),
77 | EmployeesThatAttendedId = Guid.Parse("f484ad8f-78fd-46d1-9f87-bbb1e676e37f") },
78 | new { AttendedCoursesId = Guid.Parse("844e14ce-c055-49e9-9610-855669c9859b"),
79 | EmployeesThatAttendedId = Guid.Parse("f484ad8f-78fd-46d1-9f87-bbb1e676e37f") }
80 | }
81 | ));
82 |
83 | modelBuilder.Entity()
84 | .HasData(
85 | new ExternalEmployee("Amanda", "Smith", "IT for Everyone, Inc")
86 | {
87 | Id = Guid.Parse("72f2f5fe-e50c-4966-8420-d50258aefdcb")
88 | });
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/DataAccess/Entities/Course.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 |
4 | namespace EmployeeManagement.DataAccess.Entities
5 | {
6 | public class Course
7 | {
8 | [Key]
9 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
10 | public Guid Id { get; set; }
11 | public bool IsNew { get; set; } = true;
12 | public string Title { get; set; }
13 | public List EmployeesThatAttended { get; set; }
14 | = new List();
15 |
16 | public Course(string title)
17 | {
18 | Title = title;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/DataAccess/Entities/Employee.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 |
4 | namespace EmployeeManagement.DataAccess.Entities
5 | {
6 | ///
7 | /// Base class for all employees
8 | ///
9 | public abstract class Employee
10 | {
11 | [Key]
12 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
13 | public Guid Id { get; set; }
14 |
15 | [Required]
16 | [MaxLength(100)]
17 | public string FirstName { get; set; }
18 |
19 | [Required]
20 | [MaxLength(100)]
21 | public string LastName { get; set; }
22 |
23 | [NotMapped]
24 | public string FullName
25 | {
26 | get { return $"{FirstName} {LastName}"; }
27 | }
28 |
29 | public Employee(
30 | string firstName,
31 | string lastName)
32 | {
33 | FirstName = firstName;
34 | LastName = lastName;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/DataAccess/Entities/ExternalEmployee.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.DataAccess.Entities
2 | {
3 | public class ExternalEmployee : Employee
4 | {
5 | public string Company { get; set; }
6 |
7 | public ExternalEmployee(
8 | string firstName,
9 | string lastName,
10 | string company)
11 | : base(firstName, lastName)
12 | {
13 | Company = company;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/DataAccess/Entities/InternalEmployee.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 |
4 | namespace EmployeeManagement.DataAccess.Entities
5 | {
6 | public class InternalEmployee : Employee
7 | {
8 | [Required]
9 | public int YearsInService { get; set; }
10 |
11 | [NotMapped]
12 | public decimal SuggestedBonus { get; set; }
13 |
14 | [Required]
15 | public decimal Salary { get; set; }
16 |
17 | [Required]
18 | public bool MinimumRaiseGiven { get; set; }
19 |
20 | public List AttendedCourses { get; set; } = new List();
21 |
22 | [Required]
23 | public int JobLevel { get; set; }
24 |
25 | public InternalEmployee(
26 | string firstName,
27 | string lastName,
28 | int yearsInService,
29 | decimal salary,
30 | bool minimumRaiseGiven,
31 | int jobLevel)
32 | : base(firstName, lastName)
33 | {
34 | YearsInService = yearsInService;
35 | Salary = salary;
36 | MinimumRaiseGiven = minimumRaiseGiven;
37 | JobLevel = jobLevel;
38 | }
39 |
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/DataAccess/Services/EmployeeManagementRepository.cs:
--------------------------------------------------------------------------------
1 | using EmployeeManagement.DataAccess.DbContexts;
2 | using EmployeeManagement.DataAccess.Entities;
3 | using Microsoft.EntityFrameworkCore;
4 |
5 | namespace EmployeeManagement.DataAccess.Services
6 | {
7 | public class EmployeeManagementRepository : IEmployeeManagementRepository
8 | {
9 | private readonly EmployeeDbContext _context;
10 |
11 | public EmployeeManagementRepository(EmployeeDbContext context)
12 | {
13 | _context = context ?? throw new ArgumentNullException(nameof(context));
14 | }
15 |
16 | public async Task> GetInternalEmployeesAsync()
17 | {
18 | return await _context.InternalEmployees
19 | .Include(e => e.AttendedCourses)
20 | .ToListAsync();
21 | }
22 |
23 | public async Task GetInternalEmployeeAsync(Guid employeeId)
24 | {
25 | return await _context.InternalEmployees
26 | .Include(e => e.AttendedCourses)
27 | .FirstOrDefaultAsync(e => e.Id == employeeId);
28 | }
29 |
30 | public InternalEmployee? GetInternalEmployee(Guid employeeId)
31 | {
32 | return _context.InternalEmployees
33 | .Include(e => e.AttendedCourses)
34 | .FirstOrDefault(e => e.Id == employeeId);
35 | }
36 |
37 | public async Task GetCourseAsync(Guid courseId)
38 | {
39 | return await _context.Courses.FirstOrDefaultAsync(e => e.Id == courseId);
40 | }
41 |
42 | public Course? GetCourse(Guid courseId)
43 | {
44 | return _context.Courses.FirstOrDefault(e => e.Id == courseId);
45 | }
46 |
47 | public List GetCourses(params Guid[] courseIds)
48 | {
49 | List coursesToReturn = new();
50 | foreach (var courseId in courseIds)
51 | {
52 | var course = GetCourse(courseId);
53 | if (course != null)
54 | {
55 | coursesToReturn.Add(course);
56 | }
57 | }
58 | return coursesToReturn;
59 | }
60 |
61 | public async Task> GetCoursesAsync(params Guid[] courseIds)
62 | {
63 | List coursesToReturn = new();
64 | foreach (var courseId in courseIds)
65 | {
66 | var course = await GetCourseAsync(courseId);
67 | if (course != null)
68 | {
69 | coursesToReturn.Add(course);
70 | }
71 | }
72 | return coursesToReturn;
73 | }
74 |
75 | public void AddInternalEmployee(InternalEmployee internalEmployee)
76 | {
77 | _context.InternalEmployees.Add(internalEmployee);
78 | }
79 |
80 | public async Task SaveChangesAsync()
81 | {
82 | await _context.SaveChangesAsync();
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/DataAccess/Services/IEmployeeManagementRepository.cs:
--------------------------------------------------------------------------------
1 | using EmployeeManagement.DataAccess.Entities;
2 |
3 | namespace EmployeeManagement.DataAccess.Services
4 | {
5 | public interface IEmployeeManagementRepository
6 | {
7 | Task> GetInternalEmployeesAsync();
8 |
9 | InternalEmployee? GetInternalEmployee(Guid employeeId);
10 |
11 | Task GetInternalEmployeeAsync(Guid employeeId);
12 |
13 | Task GetCourseAsync(Guid courseId);
14 |
15 | Course? GetCourse(Guid courseId);
16 |
17 | List GetCourses(params Guid[] courseIds);
18 |
19 | Task> GetCoursesAsync(params Guid[] courseIds);
20 |
21 | void AddInternalEmployee(InternalEmployee internalEmployee);
22 |
23 | Task SaveChangesAsync();
24 | }
25 | }
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/EmployeeManagement.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | all
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/EmployeeManagement.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinDockx/UnitTestingAspNetCoreMVC/37d75208496b50ca2cfc0b07ec8ffb21dd8294e4/Starter files/EmployeeManagement/EmployeeManagement.db
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/MapperProfiles/CourseProfile.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using EmployeeManagement.DataAccess.Entities;
3 |
4 | namespace EmployeeManagement.MapperProfiles
5 | {
6 | public class CourseProfile : Profile
7 | {
8 | public CourseProfile()
9 | {
10 | CreateMap();
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/MapperProfiles/EmployeeProfile.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using EmployeeManagement.DataAccess.Entities;
3 |
4 | namespace EmployeeManagement.MapperProfiles
5 | {
6 | public class EmployeeProfile : Profile
7 | {
8 | public EmployeeProfile()
9 | {
10 | CreateMap();
11 | CreateMap();
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/MapperProfiles/StatisticsProfile.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Microsoft.AspNetCore.Http.Features;
3 |
4 | namespace EmployeeManagement.MapperProfiles
5 | {
6 | public class StatisticsProfile : Profile
7 | {
8 | public StatisticsProfile()
9 | {
10 | CreateMap();
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Middleware/EmployeeManagementSecurityHeadersMiddleware.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.Middleware
2 | {
3 | public class EmployeeManagementSecurityHeadersMiddleware
4 | {
5 | private readonly RequestDelegate _next;
6 |
7 | public EmployeeManagementSecurityHeadersMiddleware(RequestDelegate next)
8 | {
9 | _next = next;
10 | }
11 |
12 |
13 | public async Task InvokeAsync(HttpContext context)
14 | {
15 | IHeaderDictionary headers = context.Response.Headers;
16 |
17 | // Add CSP + X-Content-Type
18 | headers["Content-Security-Policy"] = "default-src 'self';frame-ancestors 'none';";
19 | headers["X-Content-Type-Options"] = "nosniff";
20 |
21 | await _next(context);
22 | }
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Program.cs:
--------------------------------------------------------------------------------
1 | using EmployeeManagement;
2 | using EmployeeManagement.Business;
3 | using EmployeeManagement.DataAccess.DbContexts;
4 | using EmployeeManagement.DataAccess.Services;
5 | using EmployeeManagement.Middleware;
6 | using Microsoft.EntityFrameworkCore;
7 |
8 | var builder = WebApplication.CreateBuilder(args);
9 |
10 | // Add services to the container.
11 | builder.Services.AddControllersWithViews();
12 |
13 |
14 |
15 | // add HttpClient support
16 | builder.Services.AddHttpClient("TopLevelManagementAPIClient");
17 |
18 | // add AutoMapper for mapping between entities and viewmodels
19 | builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
20 |
21 | // add support for Sessions (requires a store)
22 | builder.Services.AddDistributedMemoryCache();
23 | builder.Services.AddSession();
24 |
25 | // add other services
26 | builder.Services.RegisterBusinessServices();
27 | builder.Services.RegisterDataServices(builder.Configuration);
28 |
29 | var app = builder.Build();
30 |
31 | // Configure the HTTP request pipeline.
32 |
33 | // custom middleware
34 | app.UseMiddleware();
35 |
36 | app.UseStaticFiles();
37 |
38 | app.UseRouting();
39 |
40 | app.UseAuthorization();
41 |
42 | app.UseSession();
43 |
44 | app.MapControllerRoute(
45 | name: "default",
46 | pattern: "{controller=EmployeeOverview}/{action=Index}/{id?}");
47 |
48 | app.Run();
49 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:28969",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "EmployeeManagement": {
12 | "commandName": "Project",
13 | "dotnetRunMessages": true,
14 | "launchBrowser": true,
15 | "applicationUrl": "http://localhost:5129",
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 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/ServiceRegistrationExtensions.cs:
--------------------------------------------------------------------------------
1 | using EmployeeManagement.Business;
2 | using EmployeeManagement.DataAccess.DbContexts;
3 | using EmployeeManagement.DataAccess.Services;
4 | using Microsoft.EntityFrameworkCore;
5 |
6 | namespace EmployeeManagement
7 | {
8 | public static class ServiceRegistrationExtensions
9 | {
10 | public static IServiceCollection RegisterBusinessServices(
11 | this IServiceCollection services)
12 | {
13 | services.AddScoped();
14 | services.AddScoped();
15 | services.AddScoped();
16 | return services;
17 | }
18 |
19 | public static IServiceCollection RegisterDataServices(
20 | this IServiceCollection services, IConfiguration configuration)
21 | {
22 | // add the DbContext
23 | services.AddDbContext(options =>
24 | options.UseSqlite(configuration.GetConnectionString("EmployeeManagementDB")));
25 |
26 | // register the repository
27 | services.AddScoped();
28 | return services;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/ViewModels/CourseViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.ViewModels
2 | {
3 | public class CourseViewModel
4 | {
5 | public Guid Id { get; set; }
6 | public bool IsNew { get; set; }
7 | public string Title { get; set; } = string.Empty;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/ViewModels/CreateInternalEmployeeViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace EmployeeManagement.ViewModels
4 | {
5 | public class CreateInternalEmployeeViewModel
6 | {
7 | [Required]
8 | [MaxLength(100)]
9 | public string FirstName { get; set; } = string.Empty;
10 |
11 | [Required]
12 | [MaxLength(100)]
13 | public string LastName { get; set; } = string.Empty;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/ViewModels/EmployeeOverviewViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.ViewModels
2 | {
3 | public class EmployeeOverviewViewModel
4 | {
5 | public List InternalEmployees { get; set; }
6 |
7 | public EmployeeOverviewViewModel(
8 | IEnumerable internalEmployeeViewModels)
9 | {
10 | InternalEmployees = internalEmployeeViewModels.ToList();
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/ViewModels/ErrorViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.ViewModels
2 | {
3 | public class ErrorViewModel
4 | {
5 | public string? RequestId { get; set; }
6 |
7 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
8 | }
9 | }
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/ViewModels/InternalEmployeeDetailViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.ViewModels
2 | {
3 | public class InternalEmployeeDetailViewModel
4 | {
5 | public Guid Id { get; set; }
6 |
7 | public string FirstName { get; set; } = string.Empty;
8 |
9 | public string LastName { get; set; } = string.Empty;
10 |
11 | public string FullName
12 | {
13 | get { return $"{FirstName} {LastName}"; }
14 | }
15 |
16 | public int YearsInService { get; set; }
17 |
18 | public decimal SuggestedBonus { get; set; }
19 |
20 | public decimal Salary { get; set; }
21 |
22 | public bool MinimumRaiseGiven { get; set; }
23 |
24 | public int JobLevel { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/ViewModels/InternalEmployeeForOverviewViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace EmployeeManagement.ViewModels
4 | {
5 | public class InternalEmployeeForOverviewViewModel
6 | {
7 | public Guid Id { get; set; }
8 |
9 | public string FirstName { get; set; } = string.Empty;
10 |
11 | public string LastName { get; set; } = string.Empty;
12 |
13 | public string FullName
14 | {
15 | get { return $"{FirstName} {LastName}"; }
16 | }
17 |
18 | public int YearsInService { get; set; }
19 |
20 | public decimal SuggestedBonus { get; set; }
21 |
22 | public decimal Salary { get; set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/ViewModels/StatisticsViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace EmployeeManagement.ViewModels
2 | {
3 | public class StatisticsViewModel
4 | {
5 | public string LocalIpAddress { get; set; } = string.Empty;
6 | public int LocalPort { get; set; }
7 | public string RemoteIpAddress { get; set; } = string.Empty;
8 | public int RemotePort { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/EmployeeOverview/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model EmployeeManagement.ViewModels.EmployeeOverviewViewModel
2 | @{
3 | ViewData["Title"] = "Employee Management";
4 | }
5 |
6 |
7 | Employee Management
8 |
9 |
10 | Add new internal employee
11 |
12 |
13 |
14 |
15 |
16 | @Html.DisplayNameFor(model => model.InternalEmployees[0].FullName)
17 | |
18 |
19 | @Html.DisplayNameFor(model => model.InternalEmployees[0].Salary)
20 | |
21 |
22 | @Html.DisplayNameFor(model => model.InternalEmployees[0].YearsInService)
23 | |
24 |
25 | @Html.DisplayNameFor(model => model.InternalEmployees[0].SuggestedBonus)
26 | |
27 |
28 |
29 |
30 | @foreach (var item in Model.InternalEmployees)
31 | {
32 |
33 |
34 | @Html.DisplayFor(modelItem => item.FullName)
35 | |
36 |
37 | @Html.DisplayFor(modelItem => item.Salary)
38 | |
39 |
40 | @Html.DisplayFor(modelItem => item.YearsInService)
41 | |
42 |
43 | @Html.DisplayFor(modelItem => item.SuggestedBonus)
44 | |
45 |
46 | Details
48 | |
49 |
50 | }
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/InternalEmployee/AddInternalEmployee.cshtml:
--------------------------------------------------------------------------------
1 | @model EmployeeManagement.ViewModels.CreateInternalEmployeeViewModel
2 | @{
3 | ViewData["Title"] = "Add Internal Employee";
4 | }
5 |
6 | Add Internal Employee
7 |
8 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/InternalEmployee/InternalEmployeeDetails.cshtml:
--------------------------------------------------------------------------------
1 | @model EmployeeManagement.ViewModels.InternalEmployeeDetailViewModel
2 | @{
3 | ViewData["Title"] = "Internal Employee Details";
4 | }
5 |
6 |
7 |
Name:
8 |
@Model?.FullName
9 |
10 |
11 |
Salary:
12 |
@Model?.Salary
13 |
14 |
15 |
Years in service:
16 |
@Model?.YearsInService
17 |
18 |
19 |
Suggested bonus:
20 |
@Model?.SuggestedBonus
21 |
22 |
23 |
Current job level:
24 |
@Model?.JobLevel
25 |
26 |
27 |
33 |
34 |
35 |
@ViewBag.PromotionRequestMessage
36 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model ErrorViewModel
2 | @{
3 | ViewData["Title"] = "Error";
4 | }
5 |
6 | Error.
7 | An error occurred while processing your request.
8 |
9 | @if (Model?.ShowRequestId ?? false)
10 | {
11 |
12 | Request ID: @Model?.RequestId
13 |
14 | }
15 |
16 | Development Mode
17 |
18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | The Development environment shouldn't be enabled for deployed applications.
22 | It can result in displaying sensitive information from exceptions to end users.
23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
24 | and restarting the app.
25 |
26 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @ViewData["Title"] - Employee Management
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
32 | @RenderBody()
33 |
34 |
35 |
36 |
38 |
39 |
40 |
41 | @await RenderSectionAsync("Scripts", required: false)
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/Shared/_Layout.cshtml.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | for details on configuring this project to bundle and minify static web assets. */
3 |
4 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | a {
11 | color: #0077cc;
12 | }
13 |
14 | .btn-primary {
15 | color: #fff;
16 | background-color: #1b6ec2;
17 | border-color: #1861ac;
18 | }
19 |
20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
21 | color: #fff;
22 | background-color: #1b6ec2;
23 | border-color: #1861ac;
24 | }
25 |
26 | .border-top {
27 | border-top: 1px solid #e5e5e5;
28 | }
29 | .border-bottom {
30 | border-bottom: 1px solid #e5e5e5;
31 | }
32 |
33 | .box-shadow {
34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
35 | }
36 |
37 | button.accept-policy {
38 | font-size: 1rem;
39 | line-height: inherit;
40 | }
41 |
42 | .footer {
43 | position: absolute;
44 | bottom: 0;
45 | width: 100%;
46 | white-space: nowrap;
47 | line-height: 60px;
48 | }
49 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/Statistics/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model EmployeeManagement.ViewModels.StatisticsViewModel
2 | @{
3 | ViewData["Title"] = "Statistics";
4 | }
5 |
6 |
7 | Statistics
8 |
9 | Local address: @Model?.LocalIpAddress, port @Model?.LocalPort
10 | Remote address: @Model?.RemoteIpAddress, port @Model?.RemotePort
11 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using EmployeeManagement
2 | @using EmployeeManagement.ViewModels
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "ConnectionStrings": {
10 | "EmployeeManagementDB": "Data Source=EmployeeManagement.db"
11 | //"EmployeeManagementDB": "Data Source=:memory:"
12 | },
13 | "TopLevelManagementAPIRoot": "http://localhost:5057"
14 | }
15 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 14px;
3 | }
4 |
5 | @media (min-width: 768px) {
6 | html {
7 | font-size: 16px;
8 | }
9 | }
10 |
11 | html {
12 | position: relative;
13 | min-height: 100%;
14 | }
15 |
16 | body {
17 | margin-bottom: 60px;
18 | }
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinDockx/UnitTestingAspNetCoreMVC/37d75208496b50ca2cfc0b07ec8ffb21dd8294e4/Starter files/EmployeeManagement/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your JavaScript code.
5 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2021 Twitter, Inc.
4 | Copyright (c) 2011-2021 The Bootstrap Authors
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 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) .NET Foundation. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js:
--------------------------------------------------------------------------------
1 | // Unobtrusive validation support library for jQuery and jQuery Validate
2 | // Copyright (c) .NET Foundation. All rights reserved.
3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
4 | // @version v3.2.11
5 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery-validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(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 u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=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),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("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 m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(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(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.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)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.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)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.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)}),m.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)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive});
6 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/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 |
--------------------------------------------------------------------------------
/Starter files/EmployeeManagement/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors, https://js.foundation/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | All files located in the node_modules and external directories are
34 | externally maintained libraries used by this software which have their
35 | own licenses; we recommend you read them, as their terms may differ from
36 | the terms above.
37 |
--------------------------------------------------------------------------------
/Starter files/TopLevelManagement/Controllers/PromotionEligibilitiesController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace TopLevelManagement.Controllers
4 | {
5 | [ApiController]
6 | [Route("api/promotioneligibilities")]
7 | public class PromotionEligibilitiesController : ControllerBase
8 | {
9 | [HttpGet("{employeeId}")]
10 | public IActionResult EmployeeIsEligibleForPromotion(Guid employeeId)
11 | {
12 | // For demo purposes, Megan (id = 72f2f5fe-e50c-4966-8420-d50258aefdcb)
13 | // is eligible for promotion, other employees aren't
14 | if (employeeId == Guid.Parse("72f2f5fe-e50c-4966-8420-d50258aefdcb"))
15 | {
16 | return Ok(new { EligibleForPromotion = true });
17 | }
18 |
19 | return Ok(new { EligibleForPromotion = false });
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/Starter files/TopLevelManagement/Program.cs:
--------------------------------------------------------------------------------
1 | var builder = WebApplication.CreateBuilder(args);
2 |
3 | // Add services to the container.
4 |
5 | builder.Services.AddControllers();
6 |
7 | var app = builder.Build();
8 |
9 | // Configure the HTTP request pipeline.
10 |
11 | app.UseAuthorization();
12 |
13 | app.MapControllers();
14 |
15 | app.Run();
16 |
--------------------------------------------------------------------------------
/Starter files/TopLevelManagement/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:13433",
8 | "sslPort": 0
9 | }
10 | },
11 | "profiles": {
12 | "TopLevelManagement.API": {
13 | "commandName": "Project",
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | },
17 | "applicationUrl": "http://localhost:5057",
18 | "dotnetRunMessages": true
19 | },
20 | "IIS Express": {
21 | "commandName": "IISExpress",
22 | "launchBrowser": true,
23 | "launchUrl": "weatherforecast",
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development"
26 | }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/Starter files/TopLevelManagement/TopLevelManagement.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | true
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Starter files/TopLevelManagement/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Starter files/TopLevelManagement/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------