├── src ├── Web │ ├── FunApp.Web │ │ ├── wwwroot │ │ │ ├── js │ │ │ │ ├── site.min.js │ │ │ │ └── site.js │ │ │ ├── css │ │ │ │ ├── site.min.css │ │ │ │ └── site.css │ │ │ └── lib │ │ │ │ ├── bootstrap │ │ │ │ ├── dist │ │ │ │ │ └── js │ │ │ │ │ │ └── npm.js │ │ │ │ ├── .bower.json │ │ │ │ └── LICENSE │ │ │ │ ├── jquery-validation-unobtrusive │ │ │ │ ├── .bower.json │ │ │ │ └── LICENSE.txt │ │ │ │ ├── jquery │ │ │ │ ├── .bower.json │ │ │ │ └── LICENSE.txt │ │ │ │ └── jquery-validation │ │ │ │ ├── .bower.json │ │ │ │ └── LICENSE.md │ │ ├── Areas │ │ │ ├── Administration │ │ │ │ ├── Views │ │ │ │ │ ├── Categories │ │ │ │ │ │ ├── Edit.cshtml │ │ │ │ │ │ ├── Create.cshtml │ │ │ │ │ │ ├── Delete.cshtml │ │ │ │ │ │ └── Index.cshtml │ │ │ │ │ ├── _ViewStart.cshtml │ │ │ │ │ └── _ViewImports.cshtml │ │ │ │ ├── Pages │ │ │ │ │ ├── _ViewStart.cshtml │ │ │ │ │ ├── _ViewImports.cshtml │ │ │ │ │ ├── Test.cshtml │ │ │ │ │ └── Test.cshtml.cs │ │ │ │ ├── Models │ │ │ │ │ └── Categories │ │ │ │ │ │ ├── DeleteCategoryInputModel.cs │ │ │ │ │ │ ├── CreateCategoryInputModel.cs │ │ │ │ │ │ └── EditCategoryInputModel.cs │ │ │ │ ├── AdministrationBaseController.cs │ │ │ │ └── Controllers │ │ │ │ │ └── CategoriesController.cs │ │ │ ├── MyFeature │ │ │ │ └── Pages │ │ │ │ │ └── _ViewStart.cshtml │ │ │ └── Identity │ │ │ │ └── Pages │ │ │ │ ├── Account │ │ │ │ ├── _ViewImports.cshtml │ │ │ │ ├── Manage │ │ │ │ │ ├── _ViewImports.cshtml │ │ │ │ │ ├── DownloadPersonalData.cshtml │ │ │ │ │ ├── _StatusMessage.cshtml │ │ │ │ │ ├── _Layout.cshtml │ │ │ │ │ ├── Disable2fa.cshtml │ │ │ │ │ ├── PersonalData.cshtml │ │ │ │ │ ├── _ManageNav.cshtml │ │ │ │ │ ├── ResetAuthenticator.cshtml │ │ │ │ │ ├── GenerateRecoveryCodes.cshtml │ │ │ │ │ ├── PersonalData.cshtml.cs │ │ │ │ │ ├── DeletePersonalData.cshtml │ │ │ │ │ ├── SetPassword.cshtml │ │ │ │ │ ├── ChangePassword.cshtml │ │ │ │ │ ├── ManageNavPages.cs │ │ │ │ │ ├── Index.cshtml │ │ │ │ │ ├── DownloadPersonalData.cshtml.cs │ │ │ │ │ ├── ExternalLogins.cshtml │ │ │ │ │ ├── TwoFactorAuthentication.cshtml │ │ │ │ │ ├── ResetAuthenticator.cshtml.cs │ │ │ │ │ ├── Disable2fa.cshtml.cs │ │ │ │ │ ├── EnableAuthenticator.cshtml │ │ │ │ │ ├── TwoFactorAuthentication.cshtml.cs │ │ │ │ │ ├── GenerateRecoveryCodes.cshtml.cs │ │ │ │ │ ├── DeletePersonalData.cshtml.cs │ │ │ │ │ ├── SetPassword.cshtml.cs │ │ │ │ │ └── ChangePassword.cshtml.cs │ │ │ │ ├── Logout.cshtml │ │ │ │ ├── ConfirmEmail.cshtml │ │ │ │ ├── ForgotPasswordConfirmation.cshtml │ │ │ │ ├── AccessDenied.cshtml │ │ │ │ ├── Lockout.cshtml │ │ │ │ ├── ResetPasswordConfirmation.cshtml │ │ │ │ ├── AccessDenied.cshtml.cs │ │ │ │ ├── Lockout.cshtml.cs │ │ │ │ ├── ForgotPasswordConfirmation.cshtml.cs │ │ │ │ ├── ResetPasswordConfirmation.cshtml.cs │ │ │ │ ├── ForgotPassword.cshtml │ │ │ │ ├── LoginWithRecoveryCode.cshtml │ │ │ │ ├── ExternalLogin.cshtml │ │ │ │ ├── Logout.cshtml.cs │ │ │ │ ├── Register.cshtml │ │ │ │ ├── ConfirmEmail.cshtml.cs │ │ │ │ ├── ResetPassword.cshtml │ │ │ │ ├── LoginWith2fa.cshtml │ │ │ │ ├── ForgotPassword.cshtml.cs │ │ │ │ ├── ResetPassword.cshtml.cs │ │ │ │ ├── LoginWithRecoveryCode.cshtml.cs │ │ │ │ ├── Login.cshtml │ │ │ │ ├── LoginWith2fa.cshtml.cs │ │ │ │ ├── Login.cshtml.cs │ │ │ │ └── Register.cshtml.cs │ │ │ │ ├── _ViewStart.cshtml │ │ │ │ ├── _ViewImports.cshtml │ │ │ │ ├── Error.cshtml.cs │ │ │ │ ├── Error.cshtml │ │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── Pages │ │ │ ├── _ViewStart.cshtml │ │ │ ├── _ViewImports.cshtml │ │ │ └── Categories │ │ │ │ ├── Index.cshtml │ │ │ │ ├── Details.cshtml │ │ │ │ ├── Index.cshtml.cs │ │ │ │ └── Details.cshtml.cs │ │ ├── Views │ │ │ ├── _ViewStart.cshtml │ │ │ ├── _ViewImports.cshtml │ │ │ ├── Home │ │ │ │ ├── Privacy.cshtml │ │ │ │ ├── About.cshtml │ │ │ │ └── Index.cshtml │ │ │ ├── Categories │ │ │ │ ├── Index.cshtml │ │ │ │ └── Details.cshtml │ │ │ ├── Jokes │ │ │ │ ├── Details.cshtml │ │ │ │ └── Create.cshtml │ │ │ └── Shared │ │ │ │ ├── Error.cshtml │ │ │ │ ├── _LoginPartial.cshtml │ │ │ │ ├── _ValidationScriptsPartial.cshtml │ │ │ │ ├── _CookieConsentPartial.cshtml │ │ │ │ └── _Layout.cshtml │ │ ├── MlModels │ │ │ └── JokesCategoryModel.zip │ │ ├── appsettings.Development.json │ │ ├── Connected Services │ │ │ └── Application Insights │ │ │ │ └── ConnectedService.json │ │ ├── Controllers │ │ │ ├── BaseController.cs │ │ │ ├── CategoriesController.cs │ │ │ ├── HomeController.cs │ │ │ └── JokesController.cs │ │ ├── appsettings.json │ │ ├── Model │ │ │ ├── Jokes │ │ │ │ └── CreateJokeInputModel.cs │ │ │ └── ValidCategoryIdAttribute.cs │ │ ├── Program.cs │ │ ├── Infrastructure │ │ │ └── CustomRouteConstraint.cs │ │ └── FunApp.Web.csproj │ └── FunApp.Web.Forum │ │ ├── FunApp.Web.Forum.csproj │ │ └── Areas │ │ └── MyFeature │ │ └── Pages │ │ ├── Page1.cshtml │ │ └── Page1.cshtml.cs ├── Services │ ├── FunApp.Services.Mapping │ │ ├── IMapTo.cs │ │ ├── IMapFrom.cs │ │ ├── IHaveCustomMappings.cs │ │ ├── FunApp.Services.Mapping.csproj │ │ ├── QueryableMappingExtensions.cs │ │ └── AutoMapperConfig.cs │ ├── FunApp.Services.MachineLearning │ │ ├── IJokesCategorizer.cs │ │ ├── FunApp.Services.MachineLearning.csproj │ │ ├── JokeModel.cs │ │ ├── JokeModelPrediction.cs │ │ └── JokesCategorizer.cs │ ├── FunApp.Services.Models │ │ ├── ErrorViewModel.cs │ │ ├── Home │ │ │ ├── IndexViewModel.cs │ │ │ └── JokeViewModel.cs │ │ ├── Jokes │ │ │ ├── JokeSimpleViewModel.cs │ │ │ └── JokeDetailsViewModel.cs │ │ ├── FunApp.Services.Models.csproj │ │ └── Categories │ │ │ └── CategoryIdAndNameViewModel.cs │ └── FunApp.Services.DataServices │ │ ├── ICategoriesService.cs │ │ ├── FunApp.Services.DataServices.csproj │ │ ├── IJokesService.cs │ │ ├── CategoriesService.cs │ │ └── JokesService.cs ├── Data │ ├── FunApp.Data.Common │ │ ├── BaseModel.cs │ │ ├── FunApp.Data.Common.csproj │ │ └── IRepository.cs │ ├── FunApp.Data │ │ ├── appsettings.json │ │ ├── FunAppContext.cs │ │ ├── FunApp.Data.csproj │ │ ├── Migrations │ │ │ ├── 20181203144103_AddedRatingToJokes.cs │ │ │ └── 20181127190213_JokesAndCategoryAdded.cs │ │ ├── FunAppContextFactory.cs │ │ └── DbRepository.cs │ └── FunApp.Data.Models │ │ ├── FunAppUser.cs │ │ ├── Joke.cs │ │ ├── Category.cs │ │ └── FunApp.Data.Models.csproj └── Tests │ ├── Sandbox │ ├── appsettings.json │ ├── Sandbox.csproj │ └── Program.cs │ ├── FunApp.Web.Tests │ ├── FunApp.Web.Tests.csproj │ ├── SeleniumTests.cs │ ├── Class1.cs │ └── SeleniumServerFactory.cs │ └── FunApp.Services.DataServices.Tests │ ├── FunApp.Services.DataServices.Tests.csproj │ └── JokesServiceTests.cs ├── README.md ├── api ├── WebApiDemo │ ├── appsettings.json │ ├── appsettings.Development.json │ ├── Controllers │ │ ├── ApiController.cs │ │ └── CitiesController.cs │ ├── Data │ │ ├── CityInfo.cs │ │ └── MyDbContext.cs │ ├── WebApiDemo.csproj │ ├── Program.cs │ └── Startup.cs └── WebApiDemos.postman_collection.json ├── ml ├── JokeModel.cs ├── JokeModelPrediction.cs ├── mltest.csproj └── mltest.sln └── LICENSE /src/Web/FunApp.Web/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FunApp 2 | Demo app with jokes and funny images 3 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Views/Categories/Edit.cshtml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/MyFeature/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Web.Areas.Identity.Pages.Account -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | 2 | @{ 3 | Layout = "/Views/Shared/_Layout.cshtml"; 4 | } 5 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Web.Areas.Identity.Pages.Account.Manage 2 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Mapping/IMapTo.cs: -------------------------------------------------------------------------------- 1 | namespace FunApp.Services.Mapping 2 | { 3 | public interface IMapTo 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Web 2 | @using FunApp.Web.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Web 2 | @using FunApp.Web.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Mapping/IMapFrom.cs: -------------------------------------------------------------------------------- 1 | namespace FunApp.Services.Mapping 2 | { 3 | public interface IMapFrom 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /api/WebApiDemo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Web 2 | @using FunApp.Web.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Web 2 | @using FunApp.Web.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/MlModels/JokesCategoryModel.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftUni-CSharp-Web-Development/FunApp/HEAD/src/Web/FunApp.Web/MlModels/JokesCategoryModel.zip -------------------------------------------------------------------------------- /src/Data/FunApp.Data.Common/BaseModel.cs: -------------------------------------------------------------------------------- 1 | namespace FunApp.Data.Common 2 | { 3 | public class BaseModel 4 | { 5 | public T Id { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Tests/Sandbox/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=.;Database=FunApp;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | } 5 | } -------------------------------------------------------------------------------- /src/Data/FunApp.Data/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=.;Database=FunApp;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | } 5 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Pages/Test.cshtml: -------------------------------------------------------------------------------- 1 | @page "/test/{name:code}" 2 | @model FunApp.Web.Areas.Administration.Pages.TestModel 3 | @{ 4 | } 5 |

Test page from administration area

-------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

@ViewData["Title"]

5 | 6 |

Use this page to detail your site's privacy policy.

7 | -------------------------------------------------------------------------------- /src/Data/FunApp.Data.Common/FunApp.Data.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /api/WebApiDemo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using FunApp.Web.Areas.Identity 3 | @namespace FunApp.Web.Areas.Identity.Pages 4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 5 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.MachineLearning/IJokesCategorizer.cs: -------------------------------------------------------------------------------- 1 | namespace FunApp.Services.MachineLearning 2 | { 3 | public interface IJokesCategorizer 4 | { 5 | string Categorize(string modelFile, string jokeContent); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ml/JokeModel.cs: -------------------------------------------------------------------------------- 1 | namespace mltest 2 | { 3 | public class JokeModel 4 | { 5 | public string Id { get; set; } 6 | 7 | public string Category { get; set; } 8 | 9 | public string Content { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/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 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace FunApp.Services.Models 2 | { 3 | public class ErrorViewModel 4 | { 5 | public string RequestId { get; set; } 6 | 7 | public bool ShowRequestId => !string.IsNullOrEmpty(this.RequestId); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Logout.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LogoutModel 3 | @{ 4 | ViewData["Title"] = "Log out"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

You have successfully logged out of the application.

10 |
-------------------------------------------------------------------------------- /src/Services/FunApp.Services.Mapping/IHaveCustomMappings.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace FunApp.Services.Mapping 4 | { 5 | public interface IHaveCustomMappings 6 | { 7 | void CreateMappings(IMapperConfigurationExpression configuration); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Models/Home/IndexViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace FunApp.Services.Models.Home 4 | { 5 | public class IndexViewModel 6 | { 7 | public IEnumerable Jokes { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Data/FunApp.Data.Models/FunAppUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace FunApp.Data.Models 4 | { 5 | // Add profile data for application users by adding properties to the FunAppUser class 6 | public class FunAppUser : IdentityUser 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ConfirmEmailModel 3 | @{ 4 | ViewData["Title"] = "Confirm email"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

10 | Thank you for confirming your email. 11 |

12 |
13 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Models/Categories/DeleteCategoryInputModel.cs: -------------------------------------------------------------------------------- 1 | using FunApp.Services.Mapping; 2 | 3 | namespace FunApp.Web.Areas.Administration.Models.Categories 4 | { 5 | public class DeleteCategoryInputModel 6 | { 7 | public int Id { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Connected Services/Application Insights/ConnectedService.json: -------------------------------------------------------------------------------- 1 | { 2 | "ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider", 3 | "Version": "8.14.11009.1", 4 | "GettingStartedDocument": { 5 | "Uri": "https://go.microsoft.com/fwlink/?LinkID=798432" 6 | } 7 | } -------------------------------------------------------------------------------- /ml/JokeModelPrediction.cs: -------------------------------------------------------------------------------- 1 | namespace mltest 2 | { 3 | using Microsoft.ML.Runtime.Api; 4 | using Microsoft.ML.Runtime.Data; 5 | 6 | public class JokeModelPrediction 7 | { 8 | [ColumnName(DefaultColumnNames.PredictedLabel)] 9 | public string Category { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ForgotPasswordConfirmation 3 | @{ 4 | ViewData["Title"] = "Forgot password confirmation"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | Please check your email to reset your password. 10 |

11 | 12 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Controllers/BaseController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace FunApp.Web.Controllers 8 | { 9 | public class BaseController : Controller 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/AdministrationBaseController.cs: -------------------------------------------------------------------------------- 1 | using FunApp.Web.Controllers; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace FunApp.Web.Areas.Administration 5 | { 6 | [Area("Administration")] 7 | public class AdministrationBaseController : BaseController 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model AccessDeniedModel 3 | @{ 4 | ViewData["Title"] = "Access denied"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

You do not have access to this resource.

10 |
11 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LockoutModel 3 | @{ 4 | ViewData["Title"] = "Locked out"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

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

10 |
11 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web.Forum/FunApp.Web.Forum.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Mapping/FunApp.Services.Mapping.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetPasswordConfirmationModel 3 | @{ 4 | ViewData["Title"] = "Reset password confirmation"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | Your password has been reset. Please click here to log in. 10 |

11 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.MachineLearning/FunApp.Services.MachineLearning.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Models/Jokes/JokeSimpleViewModel.cs: -------------------------------------------------------------------------------- 1 | using FunApp.Data.Models; 2 | using FunApp.Services.Mapping; 3 | 4 | namespace FunApp.Services.Models.Jokes 5 | { 6 | public class JokeSimpleViewModel : IMapFrom 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Content { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Models/Categories/CreateCategoryInputModel.cs: -------------------------------------------------------------------------------- 1 | using FunApp.Data.Models; 2 | using FunApp.Services.Mapping; 3 | 4 | namespace FunApp.Web.Areas.Administration.Models.Categories 5 | { 6 | public class CreateCategoryInputModel : IMapTo 7 | { 8 | public string Name { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model DownloadPersonalDataModel 3 | @{ 4 | ViewData["Title"] = "Download Your Data"; 5 | ViewData["ActivePage"] = ManageNavPages.PersonalData; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 | @section Scripts { 11 | 12 | } -------------------------------------------------------------------------------- /api/WebApiDemo/Controllers/ApiController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace WebApiDemo.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class ApiController : ControllerBase 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.MachineLearning/JokeModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace FunApp.Services.MachineLearning 6 | { 7 | internal class JokeModel 8 | { 9 | public string Id { get; set; } 10 | 11 | public string Category { get; set; } 12 | 13 | public string Content { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web.Forum/Areas/MyFeature/Pages/Page1.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model FunApp.Web.Forum.MyFeature.Pages.Page1Model 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hello from Page1 in Razor Class Library 10 | 11 | 12 |

Hello from Page1 in Razor Class Library

13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=.;Database=FunApp;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Warning" 8 | } 9 | }, 10 | "AllowedHosts": "*", 11 | "ApplicationInsights": { 12 | "InstrumentationKey": "5fb77b19-62aa-43f8-8d4d-8dae13b9c495" 13 | } 14 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Model/Jokes/CreateJokeInputModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using FunApp.Services.Models.Jokes; 3 | 4 | namespace FunApp.Web.Model.Jokes 5 | { 6 | public class CreateJokeInputModel 7 | { 8 | [Required] 9 | [MinLength(20)] 10 | public string Content { get; set; } 11 | 12 | [ValidCategoryId] 13 | public int CategoryId { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web.Forum/Areas/MyFeature/Pages/Page1.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace FunApp.Web.Forum.MyFeature.Pages 9 | { 10 | public class Page1Model : PageModel 11 | { 12 | public void OnGet() 13 | { 14 | 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Pages/Test.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace FunApp.Web.Areas.Administration.Pages 9 | { 10 | public class TestModel : PageModel 11 | { 12 | public void OnGet() 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace FunApp.Web.Areas.Identity.Pages.Account 8 | { 9 | public class AccessDeniedModel : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/Data/FunApp.Data.Models/Joke.cs: -------------------------------------------------------------------------------- 1 | using FunApp.Data.Common; 2 | 3 | namespace FunApp.Data.Models 4 | { 5 | public class Joke : BaseModel 6 | { 7 | public string Content { get; set; } 8 | 9 | public int CategoryId { get; set; } 10 | 11 | public double Rating { get; set; } 12 | 13 | public int RatingVotes { get; set; } 14 | 15 | public virtual Category Category { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.MachineLearning/JokeModelPrediction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.ML.Runtime.Api; 5 | using Microsoft.ML.Runtime.Data; 6 | 7 | namespace FunApp.Services.MachineLearning 8 | { 9 | internal class JokeModelPrediction 10 | { 11 | [ColumnName(DefaultColumnNames.PredictedLabel)] 12 | public string Category { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Models/Jokes/JokeDetailsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using FunApp.Data.Models; 5 | using FunApp.Services.Mapping; 6 | 7 | namespace FunApp.Services.Models.Jokes 8 | { 9 | public class JokeDetailsViewModel : IMapFrom 10 | { 11 | public string Content { get; set; } 12 | 13 | public string CategoryName { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.DataServices/ICategoriesService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using FunApp.Services.Models.Categories; 4 | 5 | namespace FunApp.Services.DataServices 6 | { 7 | public interface ICategoriesService 8 | { 9 | IEnumerable GetAll(); 10 | 11 | bool IsCategoryIdValid(int categoryId); 12 | 13 | int? GetCategoryId(string name); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 | @if (!String.IsNullOrEmpty(Model)) 4 | { 5 | var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; 6 | 10 | } 11 | -------------------------------------------------------------------------------- /api/WebApiDemo/Data/CityInfo.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace WebApiDemo.Data 4 | { 5 | public class CityInfo 6 | { 7 | public int Id { get; set; } 8 | 9 | [Required] 10 | [MinLength(3)] 11 | public string Name { get; set; } 12 | 13 | [Range(1, long.MaxValue)] 14 | public long Population { get; set; } 15 | 16 | [Range(-100, 100)] 17 | public decimal Temperature { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Data/FunApp.Data.Models/Category.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using FunApp.Data.Common; 5 | 6 | namespace FunApp.Data.Models 7 | { 8 | public class Category : BaseModel 9 | { 10 | public Category() 11 | { 12 | this.Jokes = new HashSet(); 13 | } 14 | 15 | public string Name { get; set; } 16 | 17 | public virtual ICollection Jokes { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ml/mltest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Lockout.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace FunApp.Web.Areas.Identity.Pages.Account 9 | { 10 | [AllowAnonymous] 11 | public class LockoutModel : PageModel 12 | { 13 | public void OnGet() 14 | { 15 | 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace FunApp.Web.Areas.Identity.Pages.Account 8 | { 9 | [AllowAnonymous] 10 | public class ForgotPasswordConfirmation : PageModel 11 | { 12 | public void OnGet() 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Data/FunApp.Data.Common/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FunApp.Data.Common 8 | { 9 | public interface IRepository 10 | where TEntity : class 11 | { 12 | IQueryable All(); 13 | 14 | Task AddAsync(TEntity entity); 15 | 16 | void Delete(TEntity entity); 17 | 18 | Task SaveChangesAsync(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Pages/Categories/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model FunApp.Web.Pages.Categories.IndexModel 3 | @{ 4 | ViewData["Title"] = "Index"; 5 | } 6 | 7 |

Categories

8 |
9 | @foreach (var category in Model.Categories) 10 | { 11 | 16 | } 17 |
-------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace FunApp.Web.Areas.Identity.Pages.Account 9 | { 10 | [AllowAnonymous] 11 | public class ResetPasswordConfirmationModel : PageModel 12 | { 13 | public void OnGet() 14 | { 15 | 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Categories/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | @{ 3 | } 4 | 5 |
6 | @for (int i = 0; i < Model.Count(); i++) 7 | { 8 | 11 | @Model[i].Name 12 | 13 | 14 | if (i != @Model.Count() - 1) 15 | { 16 |
17 | } 18 | } 19 |
20 | -------------------------------------------------------------------------------- /api/WebApiDemo/WebApiDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Data/FunApp.Data.Models/FunApp.Data.Models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Areas/Identity/Pages/_Layout.cshtml"; 3 | } 4 | 5 |

Manage your account

6 | 7 |
8 |

Change your account settings

9 |
10 |
11 |
12 | 13 |
14 |
15 | @RenderBody() 16 |
17 |
18 |
19 | 20 | @section Scripts { 21 | @RenderSection("Scripts", required: false) 22 | } 23 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/wwwroot/lib/bootstrap/dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /src/Web/FunApp.Web/wwwroot/lib/jquery-validation-unobtrusive/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation-unobtrusive", 3 | "homepage": "https://github.com/aspnet/jquery-validation-unobtrusive", 4 | "version": "3.2.9", 5 | "_release": "3.2.9", 6 | "_resolution": { 7 | "type": "version", 8 | "tag": "v3.2.9", 9 | "commit": "a91f5401898e125f10771c5f5f0909d8c4c82396" 10 | }, 11 | "_source": "https://github.com/aspnet/jquery-validation-unobtrusive.git", 12 | "_target": "^3.2.9", 13 | "_originalSource": "jquery-validation-unobtrusive", 14 | "_direct": true 15 | } -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Models/FunApp.Services.Models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.DataServices/FunApp.Services.DataServices.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Models/Categories/EditCategoryInputModel.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using FunApp.Data.Models; 3 | using FunApp.Services.Mapping; 4 | 5 | namespace FunApp.Web.Areas.Administration.Models.Categories 6 | { 7 | public class EditCategoryInputModel : IMapTo, IHaveCustomMappings 8 | { 9 | public string Name { get; set; } 10 | 11 | public void CreateMappings(IMapperConfigurationExpression configuration) 12 | { 13 | configuration.CreateMap() 14 | .ForMember(dest => dest.Id, opt => opt.Ignore()); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Pages/Categories/Details.cshtml: -------------------------------------------------------------------------------- 1 | @page "" 2 | @using X.PagedList.Mvc.Core 3 | @model FunApp.Web.Pages.Categories.DetailsModel 4 | @{ 5 | ViewData["Title"] = "Details"; 6 | } 7 | 8 |
9 | @for (int i = 0; i < Model.Jokes.Count(); i++) 10 | { 11 | 14 | @Model.Jokes[i].Content 15 | 16 |
17 | } 18 |
19 | 20 | @Html.PagedListPager( 21 | Model.Jokes, 22 | page => Url.Page("Details", new { pageNumber = page, id = Model.Id })) 23 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/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 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Views/Categories/Create.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Web.Areas.Administration.Models.Categories 2 | 3 | @model CreateCategoryInputModel 4 | 5 | Create Category 6 | 7 |
8 |

Create new Jokes Category

9 |
10 |
11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
-------------------------------------------------------------------------------- /src/Web/FunApp.Web/wwwroot/lib/jquery/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "main": "dist/jquery.js", 4 | "license": "MIT", 5 | "ignore": [ 6 | "package.json" 7 | ], 8 | "keywords": [ 9 | "jquery", 10 | "javascript", 11 | "browser", 12 | "library" 13 | ], 14 | "homepage": "https://github.com/jquery/jquery-dist", 15 | "version": "3.3.1", 16 | "_release": "3.3.1", 17 | "_resolution": { 18 | "type": "version", 19 | "tag": "3.3.1", 20 | "commit": "9e8ec3d10fad04748176144f108d7355662ae75e" 21 | }, 22 | "_source": "https://github.com/jquery/jquery-dist.git", 23 | "_target": "^3.3.1", 24 | "_originalSource": "jquery", 25 | "_direct": true 26 | } -------------------------------------------------------------------------------- /api/WebApiDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace WebApiDemo 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.DataServices/IJokesService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using FunApp.Data.Models; 4 | using FunApp.Services.Models.Home; 5 | using FunApp.Services.Models.Jokes; 6 | 7 | namespace FunApp.Services.DataServices 8 | { 9 | public interface IJokesService 10 | { 11 | IEnumerable GetRandomJokes(int count); 12 | 13 | int GetCount(); 14 | 15 | Task Create(int categoryId, string content); 16 | 17 | TViewModel GetJokeById(int id); 18 | 19 | IEnumerable GetAllByCategory(int categoryId); 20 | 21 | bool AddRatingToJoke(int jokeId, int rating); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace FunApp.Web 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseApplicationInsights() 23 | .UseStartup(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Models/Home/JokeViewModel.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using FunApp.Data.Models; 3 | using FunApp.Services.Mapping; 4 | 5 | namespace FunApp.Services.Models.Home 6 | { 7 | public class IndexJokeViewModel : IMapFrom, IHaveCustomMappings 8 | { 9 | public int Id { get; set; } 10 | 11 | public string Content { get; set; } 12 | 13 | public string HtmlContent => this.Content.Replace("\n", "
\n"); 14 | 15 | public string CategoryName { get; set; } 16 | 17 | public void CreateMappings(IMapperConfigurationExpression configuration) 18 | { 19 | // configuration.CreateMap().ForMember(x => x.CategoryName, x => x.MapFrom(j => j.Category.Name)) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Jokes/Details.cshtml: -------------------------------------------------------------------------------- 1 | @model FunApp.Services.Models.Jokes.JokeDetailsViewModel 2 | 3 | @{ 4 | ViewData["Title"] = "Details"; 5 | } 6 | 7 |

Details

8 | 9 |
10 |

JokeDetailsViewModel

11 |
12 |
13 |
14 | @Html.DisplayNameFor(model => model.Content) 15 |
16 |
17 | @Html.DisplayFor(model => model.Content) 18 |
19 |
20 | @Html.DisplayNameFor(model => model.CategoryName) 21 |
22 |
23 | @Html.DisplayFor(model => model.CategoryName) 24 |
25 |
26 |
27 | 28 |
29 | Back to Index 30 |
31 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Model/ValidCategoryIdAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using FunApp.Services.DataServices; 3 | 4 | namespace FunApp.Services.Models.Jokes 5 | { 6 | public class ValidCategoryIdAttribute : ValidationAttribute 7 | { 8 | protected override ValidationResult IsValid(object value, ValidationContext validationContext) 9 | { 10 | var service = (ICategoriesService) validationContext 11 | .GetService(typeof(ICategoriesService)); 12 | 13 | if (service.IsCategoryIdValid((int) value)) 14 | { 15 | return ValidationResult.Success; 16 | } 17 | else 18 | { 19 | return new ValidationResult("Invalid category id!"); 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.AspNetCore.Authorization; 9 | using Microsoft.AspNetCore.Identity; 10 | 11 | namespace FunApp.Web.Areas.Identity.Pages 12 | { 13 | [AllowAnonymous] 14 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 15 | public class ErrorModel : PageModel 16 | { 17 | public string RequestId { get; set; } 18 | 19 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 20 | 21 | public void OnGet() 22 | { 23 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Pages/Categories/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FunApp.Services.DataServices; 6 | using FunApp.Services.Models.Categories; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | 10 | namespace FunApp.Web.Pages.Categories 11 | { 12 | public class IndexModel : PageModel 13 | { 14 | private readonly ICategoriesService categoriesService; 15 | 16 | public IndexModel(ICategoriesService categoriesService) 17 | { 18 | this.categoriesService = categoriesService; 19 | } 20 | 21 | public IEnumerable Categories { get; set; } 22 | 23 | public void OnGet() 24 | { 25 | this.Categories = this.categoriesService.GetAll(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ForgotPasswordModel 3 | @{ 4 | ViewData["Title"] = "Forgot your password?"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Enter your email.

9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | @section Scripts { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model FunApp.Services.Models.Home.IndexViewModel 2 | @{ 3 | ViewData["Title"] = "Home Page"; 4 | var number = 1.234567M; 5 | } 6 | 7 |

@number.ToString("F3")

8 |
9 | @foreach (var joke in Model.Jokes) 10 | { 11 |
12 |
13 |
14 | @Html.Raw(joke.HtmlContent) 15 |
16 | 20 |
21 |
22 | } 23 |
24 | 25 | @*@section Scripts { 26 | 29 | }*@ -------------------------------------------------------------------------------- /src/Data/FunApp.Data/FunAppContext.cs: -------------------------------------------------------------------------------- 1 | using FunApp.Data.Models; 2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace FunApp.Web.Models 6 | { 7 | public class FunAppContext : IdentityDbContext 8 | { 9 | public FunAppContext(DbContextOptions options) 10 | : base(options) 11 | { 12 | } 13 | 14 | public DbSet Categories { get; set; } 15 | 16 | public DbSet Jokes { get; set; } 17 | 18 | protected override void OnModelCreating(ModelBuilder builder) 19 | { 20 | base.OnModelCreating(builder); 21 | // Customize the ASP.NET Identity model and override the defaults if needed. 22 | // For example, you can rename the ASP.NET Identity table names and more. 23 | // Add your customizations after calling base.OnModelCreating(builder); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

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

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

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

21 |

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

24 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model FunApp.Services.Models.ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 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 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 22 |

23 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.MachineLearning/JokesCategorizer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.ML; 4 | using Microsoft.ML.Core.Data; 5 | using Microsoft.ML.Runtime.Data; 6 | 7 | namespace FunApp.Services.MachineLearning 8 | { 9 | public class JokesCategorizer : IJokesCategorizer 10 | { 11 | public string Categorize(string modelFile, string jokeContent) 12 | { 13 | var mlContext = new MLContext(seed: 0); 14 | ITransformer trainedModel; 15 | using (var stream = new FileStream(modelFile, FileMode.Open, FileAccess.Read, FileShare.Read)) 16 | { 17 | trainedModel = mlContext.Model.Load(stream); 18 | } 19 | 20 | var predFunction = trainedModel.MakePredictionFunction(mlContext); 21 | var prediction = predFunction.Predict(new JokeModel { Content = jokeContent }); 22 | return prediction.Category; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Disable2faModel 3 | @{ 4 | ViewData["Title"] = "Disable two-factor authentication (2FA)"; 5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; 6 | } 7 | 8 | 9 |

@ViewData["Title"]

10 | 11 | 21 | 22 |
23 |
24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PersonalDataModel 3 | @{ 4 | ViewData["Title"] = "Personal Data"; 5 | ViewData["ActivePage"] = ManageNavPages.PersonalData; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 |
11 |
12 |

Your account contains personal data that you have given us. This page allows you to download or delete that data.

13 |

14 | Deleting this data will permanently remove your account, and this cannot be recovered. 15 |

16 |
17 | 18 |
19 |

20 | Delete 21 |

22 |
23 |
24 | 25 | @section Scripts { 26 | 27 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Data.Models 2 | @inject SignInManager SignInManager 3 | @{ 4 | var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); 5 | } 6 | 16 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Models/Categories/CategoryIdAndNameViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using AutoMapper; 6 | using FunApp.Data.Models; 7 | using FunApp.Services.Mapping; 8 | 9 | namespace FunApp.Services.Models.Categories 10 | { 11 | public class CategoryIdAndNameViewModel : IMapFrom, IHaveCustomMappings 12 | { 13 | public int Id { get; set; } 14 | 15 | public string Name { get; set; } 16 | 17 | public string NameAndCount => 18 | $"{this.Name} ({this.CountOfAllJokes})"; 19 | 20 | // JokesCount 21 | public int CountOfAllJokes { get; set; } 22 | 23 | public void CreateMappings(IMapperConfigurationExpression configuration) 24 | { 25 | configuration.CreateMap() 26 | .ForMember(x => x.CountOfAllJokes, 27 | m => m.MapFrom(c => c.Jokes.Count())); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/wwwroot/css/site.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 | body { 4 | padding-top: 50px; 5 | padding-bottom: 20px; 6 | } 7 | 8 | /* Wrapping element */ 9 | /* Set some basic padding to keep content from hitting the edges */ 10 | .body-content { 11 | padding-left: 15px; 12 | padding-right: 15px; 13 | } 14 | 15 | /* Carousel */ 16 | .carousel-caption p { 17 | font-size: 20px; 18 | line-height: 1.4; 19 | } 20 | 21 | /* Make .svg files in the carousel display properly in older browsers */ 22 | .carousel-inner .item img[src$=".svg"] { 23 | width: 100%; 24 | } 25 | 26 | /* QR code generator */ 27 | #qrCode { 28 | margin: 15px; 29 | } 30 | 31 | /* Hide/rearrange for smaller screens */ 32 | @media screen and (max-width: 767px) { 33 | /* Hide captions */ 34 | .carousel-caption { 35 | display: none; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Tests/FunApp.Web.Tests/FunApp.Web.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Tests/FunApp.Services.DataServices.Tests/FunApp.Services.DataServices.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Views/Categories/Delete.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | 3 |
4 | Create Category 5 |
6 |
7 |
8 | @for (int i = 0; i < Model.Count(); i++) 9 | { 10 | 13 | @Model[i].Name 14 | 15 | 16 | Edit 17 | Delete 18 | 19 | if (i != @Model.Count() - 1) 20 | { 21 |
22 | } 23 | } 24 |
25 |
-------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Views/Categories/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | 3 |
4 | Create Category 5 |
6 |
7 |
8 | @for (int i = 0; i < Model.Count(); i++) 9 | { 10 | 13 | @Model[i].Name 14 | 15 | 16 | Edit 17 | Delete 18 | 19 | if (i != @Model.Count() - 1) 20 | { 21 |
22 | } 23 | } 24 |
25 |
-------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetAuthenticatorModel 3 | @{ 4 | ViewData["Title"] = "Reset authenticator key"; 5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; 6 | } 7 | 8 | 9 |

@ViewData["Title"]

10 | 20 |
21 |
22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/Data/FunApp.Data/FunApp.Data.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Data.Models 2 | @using Microsoft.AspNetCore.Identity 3 | 4 | @inject SignInManager SignInManager 5 | @inject UserManager UserManager 6 | 7 | @if (SignInManager.IsSignedIn(User)) 8 | { 9 | 19 | } 20 | else 21 | { 22 | 26 | } -------------------------------------------------------------------------------- /src/Data/FunApp.Data/Migrations/20181203144103_AddedRatingToJokes.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace FunApp.Data.Migrations 4 | { 5 | public partial class AddedRatingToJokes : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | migrationBuilder.AddColumn( 10 | name: "Rating", 11 | table: "Jokes", 12 | nullable: false, 13 | defaultValue: 0.0); 14 | 15 | migrationBuilder.AddColumn( 16 | name: "RatingVotes", 17 | table: "Jokes", 18 | nullable: false, 19 | defaultValue: 0); 20 | } 21 | 22 | protected override void Down(MigrationBuilder migrationBuilder) 23 | { 24 | migrationBuilder.DropColumn( 25 | name: "Rating", 26 | table: "Jokes"); 27 | 28 | migrationBuilder.DropColumn( 29 | name: "RatingVotes", 30 | table: "Jokes"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginWithRecoveryCodeModel 3 | @{ 4 | ViewData["Title"] = "Recovery code verification"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

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

13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 | @section Scripts { 28 | 29 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/wwwroot/lib/jquery-validation/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation", 3 | "homepage": "https://jqueryvalidation.org/", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/jquery-validation/jquery-validation.git" 7 | }, 8 | "authors": [ 9 | "Jörn Zaefferer " 10 | ], 11 | "description": "Form validation made easy", 12 | "main": "dist/jquery.validate.js", 13 | "keywords": [ 14 | "forms", 15 | "validation", 16 | "validate" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "demo", 25 | "lib" 26 | ], 27 | "dependencies": { 28 | "jquery": ">= 1.7.2" 29 | }, 30 | "version": "1.17.0", 31 | "_release": "1.17.0", 32 | "_resolution": { 33 | "type": "version", 34 | "tag": "1.17.0", 35 | "commit": "fc9b12d3bfaa2d0c04605855b896edb2934c0772" 36 | }, 37 | "_source": "https://github.com/jzaefferer/jquery-validation.git", 38 | "_target": "^1.17.0", 39 | "_originalSource": "jquery-validation", 40 | "_direct": true 41 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Infrastructure/CustomRouteConstraint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Routing; 7 | using Remotion.Linq.Utilities; 8 | 9 | namespace FunApp.Web.Infrastructure 10 | { 11 | public class CustomRouteConstraint : IRouteConstraint 12 | { 13 | public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, 14 | RouteDirection routeDirection) 15 | { 16 | var value = values.LastOrDefault(); 17 | if (string.IsNullOrWhiteSpace(value.Value?.ToString())) 18 | { 19 | return false; 20 | } 21 | 22 | var parts = value.Value.ToString().Split("-"); 23 | if (parts.Length != 2) 24 | { 25 | return false; 26 | } 27 | 28 | if (parts[1] != "code") 29 | { 30 | return false; 31 | } 32 | 33 | return int.TryParse(parts[0], out _); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SoftUni-CSharp-Web-Development 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 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | }, 34 | "version": "3.3.7", 35 | "_release": "3.3.7", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.7", 39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" 40 | }, 41 | "_source": "https://github.com/twbs/bootstrap.git", 42 | "_target": "v3.3.7", 43 | "_originalSource": "bootstrap", 44 | "_direct": true 45 | } -------------------------------------------------------------------------------- /ml/mltest.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mltest", "mltest.csproj", "{7C990774-FE56-4316-9F5E-7FA36D25952E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {7C990774-FE56-4316-9F5E-7FA36D25952E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {7C990774-FE56-4316-9F5E-7FA36D25952E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {7C990774-FE56-4316-9F5E-7FA36D25952E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {7C990774-FE56-4316-9F5E-7FA36D25952E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {147FF7ED-A253-43CA-ACD9-CBE8807202EF} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model GenerateRecoveryCodesModel 3 | @{ 4 | ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes"; 5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; 6 | } 7 | 8 | 9 |

@ViewData["Title"]

10 | 23 |
24 |
25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Mapping/QueryableMappingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using AutoMapper.QueryableExtensions; 7 | 8 | namespace FunApp.Services.Mapping 9 | { 10 | public static class QueryableMappingExtensions 11 | { 12 | public static IQueryable To( 13 | this IQueryable source, 14 | params Expression>[] membersToExpand) 15 | { 16 | if (source == null) 17 | { 18 | throw new ArgumentNullException(nameof(source)); 19 | } 20 | 21 | return source.ProjectTo(membersToExpand); 22 | } 23 | 24 | public static IQueryable To( 25 | this IQueryable source, 26 | object parameters) 27 | { 28 | if (source == null) 29 | { 30 | throw new ArgumentNullException(nameof(source)); 31 | } 32 | 33 | return source.ProjectTo(parameters); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using FunApp.Data.Models; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 9 | { 10 | public class PersonalDataModel : PageModel 11 | { 12 | private readonly UserManager _userManager; 13 | private readonly ILogger _logger; 14 | 15 | public PersonalDataModel( 16 | UserManager userManager, 17 | ILogger logger) 18 | { 19 | _userManager = userManager; 20 | _logger = logger; 21 | } 22 | 23 | public async Task OnGet() 24 | { 25 | var user = await _userManager.GetUserAsync(User); 26 | if (user == null) 27 | { 28 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 29 | } 30 | 31 | return Page(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Twitter, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Pages/Categories/Details.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FunApp.Data.Models; 6 | using FunApp.Services.DataServices; 7 | using FunApp.Services.Models.Jokes; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using X.PagedList; 11 | 12 | namespace FunApp.Web.Pages.Categories 13 | { 14 | public class DetailsModel : PageModel 15 | { 16 | private readonly IJokesService jokesService; 17 | 18 | public DetailsModel(IJokesService jokesService) 19 | { 20 | this.jokesService = jokesService; 21 | } 22 | 23 | [BindProperty(SupportsGet = true)] 24 | public int Id { get; set; } 25 | 26 | [BindProperty(SupportsGet = true)] 27 | public int? PageNumber { get; set; } 28 | 29 | public IPagedList Jokes { get; set; } 30 | 31 | public void OnGet() 32 | { 33 | var jokesInCategory = this.jokesService.GetAllByCategory(this.Id).ToList(); 34 | this.Jokes = jokesInCategory.ToPagedList(this.PageNumber ?? 1, 10); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /api/WebApiDemo/Data/MyDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace WebApiDemo.Data 8 | { 9 | public class MyDbContext : DbContext 10 | { 11 | public DbSet Cities { get; set; } 12 | 13 | public MyDbContext(DbContextOptions options) 14 | : base(options) 15 | { 16 | } 17 | 18 | public static void SeedData(MyDbContext context) 19 | { 20 | context.Cities.Add(new CityInfo 21 | { 22 | Name = "Sofia", 23 | Population = 2000000, 24 | Temperature = -1.5M, 25 | }); 26 | context.Cities.Add(new CityInfo 27 | { 28 | Name = "Plovdiv", 29 | Population = 500000, 30 | Temperature = 3.4M, 31 | }); 32 | context.Cities.Add(new CityInfo 33 | { 34 | Name = "Varna", 35 | Population = 400000, 36 | Temperature = 5M, 37 | }); 38 | context.SaveChanges(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ExternalLogin.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ExternalLoginModel 3 | @{ 4 | ViewData["Title"] = "Register"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Associate your @Model.LoginProvider account.

9 |
10 | 11 |

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

16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 |
30 | 31 | @section Scripts { 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model DeletePersonalDataModel 3 | @{ 4 | ViewData["Title"] = "Delete Personal Data"; 5 | ViewData["ActivePage"] = ManageNavPages.PersonalData; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 | 16 | 17 |
18 |
19 |
20 | @if (Model.RequirePassword) 21 | { 22 |
23 | 24 | 25 | 26 |
27 | } 28 | 29 |
30 |
31 | 32 | @section Scripts { 33 | 34 | } -------------------------------------------------------------------------------- /src/Tests/FunApp.Web.Tests/SeleniumTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Chrome; 7 | using OpenQA.Selenium.Remote; 8 | using Xunit; 9 | 10 | namespace FunApp.Web.Tests 11 | { 12 | public class SeleniumTests : IClassFixture> 13 | { 14 | private readonly SeleniumServerFactory server; 15 | private readonly IWebDriver browser; 16 | 17 | public SeleniumTests(SeleniumServerFactory server) 18 | { 19 | this.server = server; 20 | server.CreateClient(); 21 | var opts = new ChromeOptions(); 22 | opts.AddArgument("--headless"); //Optional, comment this out if you want to SEE the browser window 23 | opts.AddArgument("no-sandbox"); 24 | this.browser = new RemoteWebDriver(opts); 25 | } 26 | 27 | [Fact] 28 | public void IndexPageShouldContains20Joke() 29 | { 30 | this.browser.Navigate().GoToUrl(this.server.RootUri); 31 | Assert.Equal(20, this.browser.FindElements(By.CssSelector("div.clearfix")).Count); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /src/Data/FunApp.Data/FunAppContextFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using Microsoft.EntityFrameworkCore.Design; 6 | using FunApp.Web.Models; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.EntityFrameworkCore.Diagnostics; 9 | using Microsoft.Extensions.Configuration; 10 | 11 | namespace FunApp.Data 12 | { 13 | public class FunAppContextFactory : IDesignTimeDbContextFactory 14 | { 15 | public FunAppContext CreateDbContext(string[] args) 16 | { 17 | var configuration = new ConfigurationBuilder() 18 | .SetBasePath(Directory.GetCurrentDirectory()) 19 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 20 | .Build(); 21 | 22 | var builder = new DbContextOptionsBuilder(); 23 | 24 | var connectionString = configuration.GetConnectionString("DefaultConnection"); 25 | 26 | builder.UseSqlServer(connectionString); 27 | 28 | // Stop client query evaluation 29 | builder.ConfigureWarnings(w => w.Throw(RelationalEventId.QueryClientEvaluationWarning)); 30 | 31 | return new FunAppContext(builder.Options); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Controllers/CategoriesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FunApp.Services.DataServices; 4 | using Microsoft.AspNetCore.Mvc; 5 | using X.PagedList; 6 | 7 | namespace FunApp.Web.Controllers 8 | { 9 | public class CategoriesController : BaseController 10 | { 11 | private readonly ICategoriesService categoriesService; 12 | private readonly IJokesService jokesService; 13 | 14 | public CategoriesController( 15 | ICategoriesService categoriesService, 16 | IJokesService jokesService) 17 | { 18 | this.categoriesService = categoriesService; 19 | this.jokesService = jokesService; 20 | } 21 | 22 | public IActionResult Index() 23 | { 24 | var categories = categoriesService 25 | .GetAll() 26 | .ToList(); 27 | 28 | return this.View(categories); 29 | } 30 | 31 | public IActionResult Details(int id, int? page) 32 | { 33 | var jokesInCategory = this.jokesService.GetAllByCategory(id).ToList(); 34 | 35 | var nextPage = page ?? 1; 36 | 37 | var pagedJokes = jokesInCategory.ToPagedList(nextPage, 4); 38 | 39 | return this.View(pagedJokes); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Data/FunApp.Data/DbRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FunApp.Data.Common; 7 | using FunApp.Web.Models; 8 | using Microsoft.EntityFrameworkCore; 9 | 10 | namespace FunApp.Data 11 | { 12 | public class DbRepository : IRepository, IDisposable 13 | where TEntity : class 14 | { 15 | private readonly FunAppContext context; 16 | private DbSet dbSet; 17 | 18 | public DbRepository(FunAppContext context) 19 | { 20 | this.context = context; 21 | this.dbSet = this.context.Set(); 22 | } 23 | 24 | public Task AddAsync(TEntity entity) 25 | { 26 | return this.dbSet.AddAsync(entity); 27 | } 28 | 29 | public IQueryable All() 30 | { 31 | return this.dbSet; 32 | } 33 | 34 | public void Delete(TEntity entity) 35 | { 36 | this.dbSet.Remove(entity); 37 | } 38 | 39 | public Task SaveChangesAsync() 40 | { 41 | return this.context.SaveChangesAsync(); 42 | } 43 | 44 | public void Dispose() 45 | { 46 | this.context.Dispose(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.DataServices/CategoriesService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FunApp.Data.Common; 5 | using FunApp.Data.Models; 6 | using FunApp.Services.Mapping; 7 | using FunApp.Services.Models.Categories; 8 | 9 | namespace FunApp.Services.DataServices 10 | { 11 | public class CategoriesService : ICategoriesService 12 | { 13 | private readonly IRepository categoriesRepository; 14 | 15 | public CategoriesService(IRepository categoriesRepository) 16 | { 17 | this.categoriesRepository = categoriesRepository; 18 | } 19 | 20 | public IEnumerable GetAll() 21 | { 22 | var categories = this.categoriesRepository.All().OrderBy(x => x.Name) 23 | .To().ToList(); 24 | 25 | return categories; 26 | } 27 | 28 | public bool IsCategoryIdValid(int categoryId) 29 | { 30 | return this.categoriesRepository.All().Any(x => x.Id == categoryId); 31 | } 32 | 33 | public int? GetCategoryId(string name) 34 | { 35 | var category = this.categoriesRepository.All().FirstOrDefault(x => x.Name == name); 36 | return category?.Id; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Tests/FunApp.Web.Tests/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc.Testing; 5 | using Xunit; 6 | 7 | namespace FunApp.Web.Tests 8 | { 9 | public class Class1 : IClassFixture> 10 | { 11 | private readonly WebApplicationFactory server; 12 | 13 | public Class1(WebApplicationFactory server) 14 | { 15 | this.server = server; 16 | } 17 | 18 | [Fact] 19 | public async Task IndexPageShouldReturnStatusCode200() 20 | { 21 | var client = this.server.CreateClient(); 22 | var response = await client.GetAsync("/"); 23 | response.EnsureSuccessStatusCode(); 24 | var responseContent = await response.Content.ReadAsStringAsync(); 25 | Assert.Contains("", responseContent); 26 | } 27 | 28 | [Fact] 29 | public async Task JokesCreatePageRequiredAuthorization() 30 | { 31 | var client = this.server.CreateClient( 32 | new WebApplicationFactoryClientOptions 33 | {AllowAutoRedirect = false}); 34 | var response = await client.GetAsync("/Jokes/Create"); 35 | Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Logout.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using FunApp.Data.Models; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace FunApp.Web.Areas.Identity.Pages.Account 13 | { 14 | [AllowAnonymous] 15 | public class LogoutModel : PageModel 16 | { 17 | private readonly SignInManager<FunAppUser> _signInManager; 18 | private readonly ILogger<LogoutModel> _logger; 19 | 20 | public LogoutModel(SignInManager<FunAppUser> signInManager, ILogger<LogoutModel> logger) 21 | { 22 | _signInManager = signInManager; 23 | _logger = logger; 24 | } 25 | 26 | public void OnGet() 27 | { 28 | } 29 | 30 | public async Task<IActionResult> OnPost(string returnUrl = null) 31 | { 32 | await _signInManager.SignOutAsync(); 33 | _logger.LogInformation("User logged out."); 34 | if (returnUrl != null) 35 | { 36 | return LocalRedirect(returnUrl); 37 | } 38 | else 39 | { 40 | return Page(); 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Tests/Sandbox/Sandbox.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk"> 2 | 3 | <PropertyGroup> 4 | <OutputType>Exe</OutputType> 5 | <TargetFramework>netcoreapp2.2</TargetFramework> 6 | </PropertyGroup> 7 | 8 | <ItemGroup> 9 | <None Remove="appsettings.json" /> 10 | </ItemGroup> 11 | 12 | <ItemGroup> 13 | <Content Include="appsettings.json"> 14 | <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> 15 | <CopyToOutputDirectory>Always</CopyToOutputDirectory> 16 | </Content> 17 | </ItemGroup> 18 | 19 | 20 | <ItemGroup> 21 | <PackageReference Include="AngleSharp" Version="0.9.11" /> 22 | <PackageReference Include="CommandLineParser" Version="2.3.0" /> 23 | <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" /> 24 | <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.0" /> 25 | <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" /> 26 | <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" /> 27 | <PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" /> 28 | </ItemGroup> 29 | 30 | <ItemGroup> 31 | <ProjectReference Include="..\..\Data\FunApp.Data.Models\FunApp.Data.Models.csproj" /> 32 | <ProjectReference Include="..\..\Data\FunApp.Data\FunApp.Data.csproj" /> 33 | </ItemGroup> 34 | 35 | </Project> 36 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Categories/Details.cshtml: -------------------------------------------------------------------------------- 1 | @using X.PagedList 2 | @using X.PagedList.Mvc.Core 3 | 4 | @model IPagedList<FunApp.Services.Models.Jokes.JokeSimpleViewModel> 5 | @{ 6 | } 7 | 8 | <div class="container" style="margin-top:20px"> 9 | @for (int i = 0; i < Model.Count(); i++) 10 | { 11 | <a asp-controller="Jokes" 12 | asp-action="Details" 13 | asp-route-id="@Model[i].Id"> 14 | @Model[i].Content 15 | </a> 16 | 17 | <input type="number" min="1" max="5" class="rating_@Model[i].Id"/> 18 | <button class="btn btn-info rateButton" data-item-id="@Model[i].Id">Rate</button> 19 | 20 | if (i != @Model.Count() - 1) 21 | { 22 | <hr /> 23 | } 24 | } 25 | </div> 26 | 27 | @Html.PagedListPager(Model, page => Url.Action("Details", "Categories", new { page })) 28 | 29 | <script> 30 | $(function () { 31 | $('.rateButton').click(function (e) { 32 | var rateButton = $(e.target); 33 | var jokeId = rateButton.attr('data-item-id'); 34 | var ratingSelector = '.rating_' + jokeId; 35 | var rating = $(ratingSelector).val(); 36 | 37 | if (rating !== 0) { 38 | $.post("/Jokes/RateJoke", { jokeId, rating }) 39 | .done(function(data) { 40 | alert(data); 41 | }); 42 | } 43 | 44 | }); 45 | }); 46 | </script> 47 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model SetPasswordModel 3 | @{ 4 | ViewData["Title"] = "Set password"; 5 | ViewData["ActivePage"] = ManageNavPages.ChangePassword; 6 | } 7 | 8 | <h4>Set your password</h4> 9 | <partial name="_StatusMessage" for="StatusMessage" /> 10 | <p class="text-info"> 11 | You do not have a local username/password for this site. Add a local 12 | account so you can log in without an external login. 13 | </p> 14 | <div class="row"> 15 | <div class="col-md-6"> 16 | <form id="set-password-form" method="post"> 17 | <div asp-validation-summary="All" class="text-danger"></div> 18 | <div class="form-group"> 19 | <label asp-for="Input.NewPassword"></label> 20 | <input asp-for="Input.NewPassword" class="form-control" /> 21 | <span asp-validation-for="Input.NewPassword" class="text-danger"></span> 22 | </div> 23 | <div class="form-group"> 24 | <label asp-for="Input.ConfirmPassword"></label> 25 | <input asp-for="Input.ConfirmPassword" class="form-control" /> 26 | <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> 27 | </div> 28 | <button type="submit" class="btn btn-default">Set password</button> 29 | </form> 30 | </div> 31 | </div> 32 | 33 | @section Scripts { 34 | <partial name="_ValidationScriptsPartial" /> 35 | } 36 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model RegisterModel 3 | @{ 4 | ViewData["Title"] = "Register"; 5 | } 6 | 7 | <h2>@ViewData["Title"]</h2> 8 | 9 | <div class="row"> 10 | <div class="col-md-4"> 11 | <form asp-route-returnUrl="@Model.ReturnUrl" method="post"> 12 | <h4>Create a new account.</h4> 13 | <hr /> 14 | <div asp-validation-summary="All" class="text-danger"></div> 15 | <div class="form-group"> 16 | <label asp-for="Input.Email"></label> 17 | <input asp-for="Input.Email" class="form-control" /> 18 | <span asp-validation-for="Input.Email" class="text-danger"></span> 19 | </div> 20 | <div class="form-group"> 21 | <label asp-for="Input.Password"></label> 22 | <input asp-for="Input.Password" class="form-control" /> 23 | <span asp-validation-for="Input.Password" class="text-danger"></span> 24 | </div> 25 | <div class="form-group"> 26 | <label asp-for="Input.ConfirmPassword"></label> 27 | <input asp-for="Input.ConfirmPassword" class="form-control" /> 28 | <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> 29 | </div> 30 | <button type="submit" class="btn btn-default">Register</button> 31 | </form> 32 | </div> 33 | </div> 34 | 35 | @section Scripts { 36 | <partial name="_ValidationScriptsPartial" /> 37 | } 38 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using FunApp.Data.Models; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | 11 | namespace FunApp.Web.Areas.Identity.Pages.Account 12 | { 13 | [AllowAnonymous] 14 | public class ConfirmEmailModel : PageModel 15 | { 16 | private readonly UserManager<FunAppUser> _userManager; 17 | 18 | public ConfirmEmailModel(UserManager<FunAppUser> userManager) 19 | { 20 | _userManager = userManager; 21 | } 22 | 23 | public async Task<IActionResult> OnGetAsync(string userId, string code) 24 | { 25 | if (userId == null || code == null) 26 | { 27 | return RedirectToPage("/Index"); 28 | } 29 | 30 | var user = await _userManager.FindByIdAsync(userId); 31 | if (user == null) 32 | { 33 | return NotFound($"Unable to load user with ID '{userId}'."); 34 | } 35 | 36 | var result = await _userManager.ConfirmEmailAsync(user, code); 37 | if (!result.Succeeded) 38 | { 39 | throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':"); 40 | } 41 | 42 | return Page(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetPasswordModel 3 | @{ 4 | ViewData["Title"] = "Reset password"; 5 | } 6 | 7 | <h2>@ViewData["Title"]</h2> 8 | <h4>Reset your password.</h4> 9 | <hr /> 10 | <div class="row"> 11 | <div class="col-md-4"> 12 | <form method="post"> 13 | <div asp-validation-summary="All" class="text-danger"></div> 14 | <input asp-for="Input.Code" type="hidden" /> 15 | <div class="form-group"> 16 | <label asp-for="Input.Email"></label> 17 | <input asp-for="Input.Email" class="form-control" /> 18 | <span asp-validation-for="Input.Email" class="text-danger"></span> 19 | </div> 20 | <div class="form-group"> 21 | <label asp-for="Input.Password"></label> 22 | <input asp-for="Input.Password" class="form-control" /> 23 | <span asp-validation-for="Input.Password" class="text-danger"></span> 24 | </div> 25 | <div class="form-group"> 26 | <label asp-for="Input.ConfirmPassword"></label> 27 | <input asp-for="Input.ConfirmPassword" class="form-control" /> 28 | <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> 29 | </div> 30 | <button type="submit" class="btn btn-default">Reset</button> 31 | </form> 32 | </div> 33 | </div> 34 | 35 | @section Scripts { 36 | <partial name="_ValidationScriptsPartial" /> 37 | } 38 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ChangePasswordModel 3 | @{ 4 | ViewData["Title"] = "Change password"; 5 | ViewData["ActivePage"] = ManageNavPages.ChangePassword; 6 | } 7 | 8 | <h4>@ViewData["Title"]</h4> 9 | <partial name="_StatusMessage" for="StatusMessage" /> 10 | <div class="row"> 11 | <div class="col-md-6"> 12 | <form id="change-password-form" method="post"> 13 | <div asp-validation-summary="All" class="text-danger"></div> 14 | <div class="form-group"> 15 | <label asp-for="Input.OldPassword"></label> 16 | <input asp-for="Input.OldPassword" class="form-control" /> 17 | <span asp-validation-for="Input.OldPassword" class="text-danger"></span> 18 | </div> 19 | <div class="form-group"> 20 | <label asp-for="Input.NewPassword"></label> 21 | <input asp-for="Input.NewPassword" class="form-control" /> 22 | <span asp-validation-for="Input.NewPassword" class="text-danger"></span> 23 | </div> 24 | <div class="form-group"> 25 | <label asp-for="Input.ConfirmPassword"></label> 26 | <input asp-for="Input.ConfirmPassword" class="form-control" /> 27 | <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> 28 | </div> 29 | <button type="submit" class="btn btn-default">Update password</button> 30 | </form> 31 | </div> 32 | </div> 33 | 34 | @section Scripts { 35 | <partial name="_ValidationScriptsPartial" /> 36 | } 37 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc.Rendering; 3 | 4 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 5 | { 6 | public static class ManageNavPages 7 | { 8 | public static string Index => "Index"; 9 | 10 | public static string ChangePassword => "ChangePassword"; 11 | 12 | public static string ExternalLogins => "ExternalLogins"; 13 | 14 | public static string PersonalData => "PersonalData"; 15 | 16 | public static string TwoFactorAuthentication => "TwoFactorAuthentication"; 17 | 18 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); 19 | 20 | public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); 21 | 22 | public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); 23 | 24 | public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData); 25 | 26 | public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); 27 | 28 | private static string PageNavClass(ViewContext viewContext, string page) 29 | { 30 | var activePage = viewContext.ViewData["ActivePage"] as string 31 | ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName); 32 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using FunApp.Data.Common; 7 | using Microsoft.AspNetCore.Mvc; 8 | using FunApp.Web.Models; 9 | using FunApp.Data.Models; 10 | using FunApp.Services.DataServices; 11 | using FunApp.Services.Models; 12 | using FunApp.Services.Models.Home; 13 | 14 | namespace FunApp.Web.Controllers 15 | { 16 | public class HomeController : BaseController 17 | { 18 | private readonly IJokesService jokesService; 19 | 20 | public HomeController(IJokesService jokesService) 21 | { 22 | this.jokesService = jokesService; 23 | } 24 | 25 | public IActionResult Index(int id) 26 | { 27 | var jokes = this.jokesService.GetRandomJokes(20); 28 | var viewModel = new IndexViewModel 29 | { 30 | Jokes = jokes, 31 | }; 32 | 33 | return this.View(viewModel); 34 | } 35 | 36 | public IActionResult About() 37 | { 38 | ViewData["Message"] = $"My application has {this.jokesService.GetCount()} jokes."; 39 | 40 | return View(); 41 | } 42 | 43 | public IActionResult Privacy() 44 | { 45 | return View(); 46 | } 47 | 48 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 49 | public IActionResult Error() 50 | { 51 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/LoginWith2fa.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginWith2faModel 3 | @{ 4 | ViewData["Title"] = "Two-factor authentication"; 5 | } 6 | 7 | <h2>@ViewData["Title"]</h2> 8 | <hr /> 9 | <p>Your login is protected with an authenticator app. Enter your authenticator code below.</p> 10 | <div class="row"> 11 | <div class="col-md-4"> 12 | <form method="post" asp-route-returnUrl="@Model.ReturnUrl"> 13 | <input asp-for="RememberMe" type="hidden" /> 14 | <div asp-validation-summary="All" class="text-danger"></div> 15 | <div class="form-group"> 16 | <label asp-for="Input.TwoFactorCode"></label> 17 | <input asp-for="Input.TwoFactorCode" class="form-control" autocomplete="off" /> 18 | <span asp-validation-for="Input.TwoFactorCode" class="text-danger"></span> 19 | </div> 20 | <div class="form-group"> 21 | <div class="checkbox"> 22 | <label asp-for="Input.RememberMachine"> 23 | <input asp-for="Input.RememberMachine" /> 24 | @Html.DisplayNameFor(m => m.Input.RememberMachine) 25 | </label> 26 | </div> 27 | </div> 28 | <div class="form-group"> 29 | <button type="submit" class="btn btn-default">Log in</button> 30 | </div> 31 | </form> 32 | </div> 33 | </div> 34 | <p> 35 | Don't have access to your authenticator device? You can 36 | <a asp-page="./LoginWithRecoveryCode" asp-route-returnUrl="@Model.ReturnUrl">log in with a recovery code</a>. 37 | </p> 38 | 39 | @section Scripts { 40 | <partial name="_ValidationScriptsPartial" /> 41 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/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 | -------------------------------------------------------------------------------- /src/Tests/FunApp.Services.DataServices.Tests/JokesServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FunApp.Data; 6 | using FunApp.Data.Common; 7 | using FunApp.Data.Models; 8 | using FunApp.Web.Models; 9 | using Microsoft.EntityFrameworkCore; 10 | using Moq; 11 | using Xunit; 12 | 13 | namespace FunApp.Services.DataServices.Tests 14 | { 15 | public class JokesServiceTests 16 | { 17 | [Fact] 18 | public void GetCountShouldReturnsCorrectNumber() 19 | { 20 | var jokesRepository = new Mock<IRepository<Joke>>(); 21 | jokesRepository.Setup(r => r.All()).Returns(new List<Joke> 22 | { 23 | new Joke(), 24 | new Joke(), 25 | new Joke(), 26 | }.AsQueryable()); 27 | var service = new JokesService(jokesRepository.Object, null); 28 | Assert.Equal(3, service.GetCount()); 29 | jokesRepository.Verify(x => x.All(), Times.Once); 30 | } 31 | 32 | [Fact] 33 | public async Task GetCountShouldReturnsCorrectNumberUsingDbContext() 34 | { 35 | var options = new DbContextOptionsBuilder<FunAppContext>() 36 | .UseInMemoryDatabase(databaseName: "Find_User_Database") // Give a Unique name to the DB 37 | .Options; 38 | var dbContext = new FunAppContext(options); 39 | dbContext.Jokes.Add(new Joke()); 40 | dbContext.Jokes.Add(new Joke()); 41 | dbContext.Jokes.Add(new Joke()); 42 | await dbContext.SaveChangesAsync(); 43 | 44 | var repository = new DbRepository<Joke>(dbContext); 45 | var jokesService = new JokesService(repository, null); 46 | var count = jokesService.GetCount(); 47 | Assert.Equal(3, count); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Shared/_CookieConsentPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Http.Features 2 | 3 | @{ 4 | var consentFeature = Context.Features.Get<ITrackingConsentFeature>(); 5 | var showBanner = !consentFeature?.CanTrack ?? false; 6 | var cookieString = consentFeature?.CreateConsentCookie(); 7 | } 8 | 9 | @if (showBanner) 10 | { 11 | <nav id="cookieConsent" class="navbar navbar-default navbar-fixed-top" role="alert"> 12 | <div class="container"> 13 | <div class="navbar-header"> 14 | <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#cookieConsent .navbar-collapse"> 15 | <span class="sr-only">Toggle cookie consent banner</span> 16 | <span class="icon-bar"></span> 17 | <span class="icon-bar"></span> 18 | <span class="icon-bar"></span> 19 | </button> 20 | <span class="navbar-brand"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span></span> 21 | </div> 22 | <div class="collapse navbar-collapse"> 23 | <p class="navbar-text"> 24 | Use this space to summarize your privacy and cookie use policy. 25 | </p> 26 | <div class="navbar-right"> 27 | <a asp-controller="Home" asp-action="Privacy" class="btn btn-info navbar-btn">Learn More</a> 28 | <button type="button" class="btn btn-default navbar-btn" data-cookie-string="@cookieString">Accept</button> 29 | </div> 30 | </div> 31 | </div> 32 | </nav> 33 | <script> 34 | (function () { 35 | document.querySelector("#cookieConsent button[data-cookie-string]").addEventListener("click", function (el) { 36 | document.cookie = el.target.dataset.cookieString; 37 | document.querySelector("#cookieConsent").classList.add("hidden"); 38 | }, false); 39 | })(); 40 | </script> 41 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Profile"; 5 | ViewData["ActivePage"] = ManageNavPages.Index; 6 | } 7 | 8 | <h4>@ViewData["Title"]</h4> 9 | <partial name="_StatusMessage" for="StatusMessage" /> 10 | <div class="row"> 11 | <div class="col-md-6"> 12 | <form id="profile-form" method="post"> 13 | <div asp-validation-summary="All" class="text-danger"></div> 14 | <div class="form-group"> 15 | <label asp-for="Username"></label> 16 | <input asp-for="Username" class="form-control" disabled /> 17 | </div> 18 | <div class="form-group"> 19 | <label asp-for="Input.Email"></label> 20 | @if (Model.IsEmailConfirmed) 21 | { 22 | <div class="input-group"> 23 | <input asp-for="Input.Email" class="form-control" /> 24 | <span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok text-success"></span></span> 25 | </div> 26 | } 27 | else 28 | { 29 | <input asp-for="Input.Email" class="form-control" /> 30 | <button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button> 31 | } 32 | <span asp-validation-for="Input.Email" class="text-danger"></span> 33 | </div> 34 | <div class="form-group"> 35 | <label asp-for="Input.PhoneNumber"></label> 36 | <input asp-for="Input.PhoneNumber" class="form-control" /> 37 | <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span> 38 | </div> 39 | <button type="submit" class="btn btn-default">Save</button> 40 | </form> 41 | </div> 42 | </div> 43 | 44 | @section Scripts { 45 | <partial name="_ValidationScriptsPartial" /> 46 | } 47 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FunApp.Data.Models; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using Microsoft.Extensions.Logging; 11 | using Newtonsoft.Json; 12 | 13 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 14 | { 15 | public class DownloadPersonalDataModel : PageModel 16 | { 17 | private readonly UserManager<FunAppUser> _userManager; 18 | private readonly ILogger<DownloadPersonalDataModel> _logger; 19 | 20 | public DownloadPersonalDataModel( 21 | UserManager<FunAppUser> userManager, 22 | ILogger<DownloadPersonalDataModel> logger) 23 | { 24 | _userManager = userManager; 25 | _logger = logger; 26 | } 27 | 28 | public async Task<IActionResult> OnPostAsync() 29 | { 30 | var user = await _userManager.GetUserAsync(User); 31 | if (user == null) 32 | { 33 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 34 | } 35 | 36 | _logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User)); 37 | 38 | // Only include personal data for download 39 | var personalData = new Dictionary<string, string>(); 40 | var personalDataProps = typeof(FunAppUser).GetProperties().Where( 41 | prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute))); 42 | foreach (var p in personalDataProps) 43 | { 44 | personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null"); 45 | } 46 | 47 | Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json"); 48 | return new FileContentResult(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(personalData)), "text/json"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /api/WebApiDemo/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Logging; 13 | using Microsoft.Extensions.Options; 14 | using WebApiDemo.Data; 15 | 16 | namespace WebApiDemo 17 | { 18 | public class Startup 19 | { 20 | public Startup(IConfiguration configuration) 21 | { 22 | Configuration = configuration; 23 | } 24 | 25 | public IConfiguration Configuration { get; } 26 | 27 | // This method gets called by the runtime. Use this method to add services to the container. 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | services.AddDbContext<MyDbContext>(options => 31 | options.UseInMemoryDatabase("MyDb")); 32 | 33 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 34 | } 35 | 36 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 37 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 38 | { 39 | if (env.IsDevelopment()) 40 | { 41 | using (var scope = app.ApplicationServices.CreateScope()) 42 | { 43 | var dbContext = scope.ServiceProvider.GetService<MyDbContext>(); 44 | MyDbContext.SeedData(dbContext); 45 | } 46 | 47 | app.UseDeveloperExceptionPage(); 48 | } 49 | else 50 | { 51 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 52 | app.UseHsts(); 53 | } 54 | 55 | app.UseHttpsRedirection(); 56 | app.UseMvc(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ExternalLoginsModel 3 | @{ 4 | ViewData["Title"] = "Manage your external logins"; 5 | ViewData["ActivePage"] = ManageNavPages.ExternalLogins; 6 | } 7 | 8 | <partial name="_StatusMessage" for="StatusMessage" /> 9 | @if (Model.CurrentLogins?.Count > 0) 10 | { 11 | <h4>Registered Logins</h4> 12 | <table class="table"> 13 | <tbody> 14 | @foreach (var login in Model.CurrentLogins) 15 | { 16 | <tr> 17 | <td>@login.LoginProvider</td> 18 | <td> 19 | @if (Model.ShowRemoveButton) 20 | { 21 | <form id="remove-login" asp-page-handler="RemoveLogin" method="post"> 22 | <div> 23 | <input asp-for="@login.LoginProvider" name="LoginProvider" type="hidden" /> 24 | <input asp-for="@login.ProviderKey" name="ProviderKey" type="hidden" /> 25 | <button type="submit" class="btn btn-default" title="Remove this @login.LoginProvider login from your account">Remove</button> 26 | </div> 27 | </form> 28 | } 29 | else 30 | { 31 | @:   32 | } 33 | </td> 34 | </tr> 35 | } 36 | </tbody> 37 | </table> 38 | } 39 | @if (Model.OtherLogins?.Count > 0) 40 | { 41 | <h4>Add another service to log in.</h4> 42 | <hr /> 43 | <form id="link-login-form" asp-page-handler="LinkLogin" method="post" class="form-horizontal"> 44 | <div id="socialLoginList"> 45 | <p> 46 | @foreach (var provider in Model.OtherLogins) 47 | { 48 | <button id="link-login-button" type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> 49 | } 50 | </p> 51 | </div> 52 | </form> 53 | } 54 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Administration/Controllers/CategoriesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using AutoMapper; 6 | using FunApp.Data.Common; 7 | using FunApp.Data.Models; 8 | using FunApp.Services.DataServices; 9 | using FunApp.Web.Areas.Administration.Models.Categories; 10 | using FunApp.Web.Controllers; 11 | using Microsoft.AspNetCore.Mvc; 12 | 13 | // For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 14 | 15 | namespace FunApp.Web.Areas.Administration.Controllers 16 | { 17 | public class CategoriesController : AdministrationBaseController 18 | { 19 | private readonly ICategoriesService categoriesService; 20 | private readonly IRepository<Category> categories; 21 | 22 | public CategoriesController( 23 | ICategoriesService categoriesService, IRepository<Category> categories) 24 | { 25 | this.categoriesService = categoriesService; 26 | this.categories = categories; 27 | } 28 | 29 | public IActionResult Index() 30 | { 31 | var categories = categoriesService 32 | .GetAll() 33 | .ToList(); 34 | 35 | return this.View(categories); 36 | } 37 | 38 | public IActionResult Create() => this.View(); 39 | 40 | [HttpPost] 41 | public async Task<IActionResult> Create(CreateCategoryInputModel model) 42 | { 43 | var newCategory = Mapper.Map<Category>(model); 44 | await this.categories.AddAsync(newCategory); 45 | await this.categories.SaveChangesAsync(); 46 | return this.RedirectToAction(nameof(Index)); 47 | } 48 | 49 | public IActionResult Edit(int id) => this.View(); 50 | 51 | [HttpPost] 52 | public IActionResult Edit(EditCategoryInputModel model) 53 | { 54 | return null; 55 | } 56 | 57 | public IActionResult Delete(int id) => this.View(); 58 | 59 | [HttpPost] 60 | public IActionResult Delete(DeleteCategoryInputModel model) 61 | { 62 | return null; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TwoFactorAuthenticationModel 3 | @{ 4 | ViewData["Title"] = "Two-factor authentication (2FA)"; 5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; 6 | } 7 | 8 | <partial name="_StatusMessage" for="StatusMessage" /> 9 | <h4>@ViewData["Title"]</h4> 10 | @if (Model.Is2faEnabled) 11 | { 12 | if (Model.RecoveryCodesLeft == 0) 13 | { 14 | <div class="alert alert-danger"> 15 | <strong>You have no recovery codes left.</strong> 16 | <p>You must <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a> before you can log in with a recovery code.</p> 17 | </div> 18 | } 19 | else if (Model.RecoveryCodesLeft == 1) 20 | { 21 | <div class="alert alert-danger"> 22 | <strong>You have 1 recovery code left.</strong> 23 | <p>You can <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p> 24 | </div> 25 | } 26 | else if (Model.RecoveryCodesLeft <= 3) 27 | { 28 | <div class="alert alert-warning"> 29 | <strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong> 30 | <p>You should <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p> 31 | </div> 32 | } 33 | 34 | if (Model.IsMachineRemembered) 35 | { 36 | <form method="post" style="display: inline-block"> 37 | <button type="submit" class="btn btn-default">Forget this browser</button> 38 | </form> 39 | } 40 | <a asp-page="./Disable2fa" class="btn btn-default">Disable 2FA</a> 41 | <a asp-page="./GenerateRecoveryCodes" class="btn btn-default">Reset recovery codes</a> 42 | } 43 | 44 | <h5>Authenticator app</h5> 45 | @if (!Model.HasAuthenticator) 46 | { 47 | <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-default">Add authenticator app</a> 48 | } 49 | else 50 | { 51 | <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-default">Setup authenticator app</a> 52 | <a id="reset-authenticator" asp-page="./ResetAuthenticator" class="btn btn-default">Reset authenticator app</a> 53 | } 54 | 55 | @section Scripts { 56 | <partial name="_ValidationScriptsPartial" /> 57 | } 58 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FunApp.Data.Models; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 12 | { 13 | public class ResetAuthenticatorModel : PageModel 14 | { 15 | UserManager<FunAppUser> _userManager; 16 | private readonly SignInManager<FunAppUser> _signInManager; 17 | ILogger<ResetAuthenticatorModel> _logger; 18 | 19 | public ResetAuthenticatorModel( 20 | UserManager<FunAppUser> userManager, 21 | SignInManager<FunAppUser> signInManager, 22 | ILogger<ResetAuthenticatorModel> logger) 23 | { 24 | _userManager = userManager; 25 | _signInManager = signInManager; 26 | _logger = logger; 27 | } 28 | 29 | [TempData] 30 | public string StatusMessage { get; set; } 31 | 32 | public async Task<IActionResult> OnGet() 33 | { 34 | var user = await _userManager.GetUserAsync(User); 35 | if (user == null) 36 | { 37 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 38 | } 39 | 40 | return Page(); 41 | } 42 | 43 | public async Task<IActionResult> OnPostAsync() 44 | { 45 | var user = await _userManager.GetUserAsync(User); 46 | if (user == null) 47 | { 48 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 49 | } 50 | 51 | await _userManager.SetTwoFactorEnabledAsync(user, false); 52 | await _userManager.ResetAuthenticatorKeyAsync(user); 53 | _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id); 54 | 55 | await _signInManager.RefreshSignInAsync(user); 56 | StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key."; 57 | 58 | return RedirectToPage("./EnableAuthenticator"); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Jokes/Create.cshtml: -------------------------------------------------------------------------------- 1 | @using FunApp.Services.Models.Categories 2 | @using Microsoft.AspNetCore.Antiforgery 3 | @inject IAntiforgery AF 4 | @model FunApp.Web.Model.Jokes.CreateJokeInputModel 5 | 6 | @{ 7 | ViewData["Title"] = "Create"; 8 | var categories = this.ViewData["Categories"] as IEnumerable<SelectListItem>; 9 | } 10 | 11 | <h2>@ViewData["Title"]</h2> 12 | <hr /> 13 | <div class="row"> 14 | <div class="col-md-4"> 15 | <form asp-action="Create"> 16 | <div asp-validation-summary="ModelOnly" class="text-danger"></div> 17 | <div class="form-group"> 18 | <label asp-for="Content" class="control-label"></label> 19 | <textarea asp-for="Content" class="form-control" rows="6" cols="50"></textarea> 20 | <span asp-validation-for="Content" class="text-danger"></span> 21 | </div> 22 | <div class="form-group"> 23 | <label asp-for="CategoryId" class="control-label"></label> 24 | <select asp-for="CategoryId" asp-items="categories" class="form-control"></select> 25 | <span asp-validation-for="CategoryId" class="text-danger"></span> 26 | </div> 27 | <div id="SuggestedCategory"></div> 28 | <div class="form-group"> 29 | <input type="submit" value="Create" class="btn btn-primary" /> 30 | <input value="Suggest category" class="btn btn-default" id="CategorizeButton" /> 31 | </div> 32 | </form> 33 | </div> 34 | </div> 35 | 36 | <div> 37 | <a asp-action="Index" asp-controller="Home">Back to Index</a> 38 | </div> 39 | 40 | @section Scripts { 41 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} 42 | 43 | <script> 44 | $("#CategorizeButton").click(function () { 45 | var joke = $("#Content").val(); 46 | var aft = "@AF.GetTokens(this.Context).RequestToken"; 47 | $.ajax({ 48 | type: "POST", 49 | url: "/Jokes/SuggestCategory", 50 | data: { joke: joke }, 51 | success: function(data) { 52 | $("#SuggestedCategory").html("Suggested category: " + data.categoryName); 53 | $("#CategoryId").val(data.categoryId); 54 | } 55 | }); 56 | }); 57 | </script> 58 | } 59 | -------------------------------------------------------------------------------- /src/Tests/FunApp.Web.Tests/SeleniumServerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Hosting.Server.Features; 8 | using Microsoft.AspNetCore.Mvc.Testing; 9 | using Microsoft.AspNetCore.TestHost; 10 | using Microsoft.Extensions.DependencyInjection; 11 | 12 | namespace FunApp.Web.Tests 13 | { 14 | public class SeleniumServerFactory<TStartup> : WebApplicationFactory<Startup> 15 | where TStartup : class 16 | { 17 | public string RootUri { get; set; } //Save this use by tests 18 | 19 | Process _process; 20 | IWebHost _host; 21 | 22 | public SeleniumServerFactory() 23 | { 24 | ClientOptions.BaseAddress = new Uri("https://localhost"); //will follow redirects by default 25 | 26 | _process = new Process() 27 | { 28 | StartInfo = new ProcessStartInfo 29 | { 30 | FileName = "selenium-standalone", 31 | Arguments = "start", 32 | UseShellExecute = true 33 | } 34 | }; 35 | _process.Start(); 36 | } 37 | 38 | protected override TestServer CreateServer(IWebHostBuilder builder) 39 | { 40 | //Real TCP port 41 | _host = builder.Build(); 42 | _host.Start(); 43 | RootUri = _host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.LastOrDefault(); //Last is https://localhost:5001! 44 | 45 | //Fake Server we won't use...this is lame. Should be cleaner, or a utility class 46 | return new TestServer(new WebHostBuilder().UseStartup<FakeStartup>()); 47 | } 48 | 49 | protected override void Dispose(bool disposing) 50 | { 51 | base.Dispose(disposing); 52 | if (disposing) 53 | { 54 | _host.Dispose(); 55 | _process.CloseMainWindow(); //Be sure to stop Selenium Standalone 56 | } 57 | } 58 | 59 | public class FakeStartup 60 | { 61 | public void ConfigureServices(IServiceCollection services) 62 | { 63 | } 64 | 65 | public void Configure() 66 | { 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Data/FunApp.Data/Migrations/20181127190213_JokesAndCategoryAdded.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace FunApp.Data.Migrations 5 | { 6 | public partial class JokesAndCategoryAdded : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "Categories", 12 | columns: table => new 13 | { 14 | Id = table.Column<int>(nullable: false) 15 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 16 | Name = table.Column<string>(nullable: true) 17 | }, 18 | constraints: table => 19 | { 20 | table.PrimaryKey("PK_Categories", x => x.Id); 21 | }); 22 | 23 | migrationBuilder.CreateTable( 24 | name: "Jokes", 25 | columns: table => new 26 | { 27 | Id = table.Column<int>(nullable: false) 28 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 29 | Content = table.Column<string>(nullable: true), 30 | CategoryId = table.Column<int>(nullable: false) 31 | }, 32 | constraints: table => 33 | { 34 | table.PrimaryKey("PK_Jokes", x => x.Id); 35 | table.ForeignKey( 36 | name: "FK_Jokes_Categories_CategoryId", 37 | column: x => x.CategoryId, 38 | principalTable: "Categories", 39 | principalColumn: "Id", 40 | onDelete: ReferentialAction.Cascade); 41 | }); 42 | 43 | migrationBuilder.CreateIndex( 44 | name: "IX_Jokes_CategoryId", 45 | table: "Jokes", 46 | column: "CategoryId"); 47 | } 48 | 49 | protected override void Down(MigrationBuilder migrationBuilder) 50 | { 51 | migrationBuilder.DropTable( 52 | name: "Jokes"); 53 | 54 | migrationBuilder.DropTable( 55 | name: "Categories"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FunApp.Data.Models; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 12 | { 13 | public class Disable2faModel : PageModel 14 | { 15 | private readonly UserManager<FunAppUser> _userManager; 16 | private readonly ILogger<Disable2faModel> _logger; 17 | 18 | public Disable2faModel( 19 | UserManager<FunAppUser> userManager, 20 | ILogger<Disable2faModel> logger) 21 | { 22 | _userManager = userManager; 23 | _logger = logger; 24 | } 25 | 26 | [TempData] 27 | public string StatusMessage { get; set; } 28 | 29 | public async Task<IActionResult> OnGet() 30 | { 31 | var user = await _userManager.GetUserAsync(User); 32 | if (user == null) 33 | { 34 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 35 | } 36 | 37 | if (!await _userManager.GetTwoFactorEnabledAsync(user)) 38 | { 39 | throw new InvalidOperationException($"Cannot disable 2FA for user with ID '{_userManager.GetUserId(User)}' as it's not currently enabled."); 40 | } 41 | 42 | return Page(); 43 | } 44 | 45 | public async Task<IActionResult> OnPostAsync() 46 | { 47 | var user = await _userManager.GetUserAsync(User); 48 | if (user == null) 49 | { 50 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 51 | } 52 | 53 | var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false); 54 | if (!disable2faResult.Succeeded) 55 | { 56 | throw new InvalidOperationException($"Unexpected error occurred disabling 2FA for user with ID '{_userManager.GetUserId(User)}'."); 57 | } 58 | 59 | _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User)); 60 | StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app"; 61 | return RedirectToPage("./TwoFactorAuthentication"); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Encodings.Web; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authorization; 7 | using FunApp.Data.Models; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.AspNetCore.Identity.UI.Services; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.AspNetCore.Mvc.RazorPages; 12 | 13 | namespace FunApp.Web.Areas.Identity.Pages.Account 14 | { 15 | [AllowAnonymous] 16 | public class ForgotPasswordModel : PageModel 17 | { 18 | private readonly UserManager<FunAppUser> _userManager; 19 | private readonly IEmailSender _emailSender; 20 | 21 | public ForgotPasswordModel(UserManager<FunAppUser> userManager, IEmailSender emailSender) 22 | { 23 | _userManager = userManager; 24 | _emailSender = emailSender; 25 | } 26 | 27 | [BindProperty] 28 | public InputModel Input { get; set; } 29 | 30 | public class InputModel 31 | { 32 | [Required] 33 | [EmailAddress] 34 | public string Email { get; set; } 35 | } 36 | 37 | public async Task<IActionResult> OnPostAsync() 38 | { 39 | if (ModelState.IsValid) 40 | { 41 | var user = await _userManager.FindByEmailAsync(Input.Email); 42 | if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) 43 | { 44 | // Don't reveal that the user does not exist or is not confirmed 45 | return RedirectToPage("./ForgotPasswordConfirmation"); 46 | } 47 | 48 | // For more information on how to enable account confirmation and password reset please 49 | // visit https://go.microsoft.com/fwlink/?LinkID=532713 50 | var code = await _userManager.GeneratePasswordResetTokenAsync(user); 51 | var callbackUrl = Url.Page( 52 | "/Account/ResetPassword", 53 | pageHandler: null, 54 | values: new { code }, 55 | protocol: Request.Scheme); 56 | 57 | await _emailSender.SendEmailAsync( 58 | Input.Email, 59 | "Reset Password", 60 | $"Please reset your password by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); 61 | 62 | return RedirectToPage("./ForgotPasswordConfirmation"); 63 | } 64 | 65 | return Page(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.DataServices/JokesService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FunApp.Data.Common; 7 | using FunApp.Data.Models; 8 | using FunApp.Services.Mapping; 9 | using FunApp.Services.Models.Home; 10 | using FunApp.Services.Models.Jokes; 11 | using Remotion.Linq.Utilities; 12 | 13 | namespace FunApp.Services.DataServices 14 | { 15 | public class JokesService : IJokesService 16 | { 17 | private readonly IRepository<Joke> jokesRepository; 18 | private readonly IRepository<Category> categoriesRepository; 19 | 20 | public JokesService( 21 | IRepository<Joke> jokesRepository, 22 | IRepository<Category> categoriesRepository) 23 | { 24 | this.jokesRepository = jokesRepository; 25 | this.categoriesRepository = categoriesRepository; 26 | } 27 | 28 | public IEnumerable<IndexJokeViewModel> GetRandomJokes(int count) 29 | { 30 | var jokes = this.jokesRepository.All() 31 | .OrderBy(x => Guid.NewGuid()) 32 | .To<IndexJokeViewModel>().Take(count).ToList(); 33 | 34 | return jokes; 35 | } 36 | 37 | public int GetCount() 38 | { 39 | return this.jokesRepository.All().Count(); 40 | } 41 | 42 | public async Task<int> Create(int categoryId, string content) 43 | { 44 | var joke = new Joke 45 | { 46 | CategoryId = categoryId, 47 | Content = content, 48 | }; 49 | 50 | await this.jokesRepository.AddAsync(joke); 51 | await this.jokesRepository.SaveChangesAsync(); 52 | 53 | return joke.Id; 54 | } 55 | 56 | public TViewModel GetJokeById<TViewModel>(int id) 57 | { 58 | var joke = this.jokesRepository.All().Where(x => x.Id == id) 59 | .To<TViewModel>().FirstOrDefault(); 60 | return joke; 61 | } 62 | 63 | public IEnumerable<JokeSimpleViewModel> GetAllByCategory(int categoryId) 64 | => this.jokesRepository 65 | .All() 66 | .Where(j => j.CategoryId == categoryId) 67 | .To<JokeSimpleViewModel>(); 68 | 69 | public bool AddRatingToJoke(int jokeId, int rating) 70 | { 71 | var joke = this.jokesRepository.All().FirstOrDefault(j => j.Id == jokeId); 72 | if (joke != null) 73 | { 74 | joke.Rating += rating; 75 | this.jokesRepository.SaveChangesAsync(); 76 | return true; 77 | } 78 | 79 | return false; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model EnableAuthenticatorModel 3 | @{ 4 | ViewData["Title"] = "Configure authenticator app"; 5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; 6 | } 7 | 8 | <partial name="_StatusMessage" for="StatusMessage" /> 9 | <h4>@ViewData["Title"]</h4> 10 | <div> 11 | <p>To use an authenticator app go through the following steps:</p> 12 | <ol class="list"> 13 | <li> 14 | <p> 15 | Download a two-factor authenticator app like Microsoft Authenticator for 16 | <a href="https://go.microsoft.com/fwlink/?Linkid=825071">Windows Phone</a>, 17 | <a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and 18 | <a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or 19 | Google Authenticator for 20 | <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en">Android</a> and 21 | <a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>. 22 | </p> 23 | </li> 24 | <li> 25 | <p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p> 26 | <div class="alert alert-info">To enable QR code generation please read our <a href="https://go.microsoft.com/fwlink/?Linkid=852423">documentation</a>.</div> 27 | <div id="qrCode"></div> 28 | <div id="qrCodeData" data-url="@Html.Raw(Model.AuthenticatorUri)"></div> 29 | </li> 30 | <li> 31 | <p> 32 | Once you have scanned the QR code or input the key above, your two factor authentication app will provide you 33 | with a unique code. Enter the code in the confirmation box below. 34 | </p> 35 | <div class="row"> 36 | <div class="col-md-6"> 37 | <form method="post"> 38 | <div class="form-group"> 39 | <label asp-for="Input.Code" class="control-label">Verification Code</label> 40 | <input asp-for="Input.Code" class="form-control" autocomplete="off" /> 41 | <span asp-validation-for="Input.Code" class="text-danger"></span> 42 | </div> 43 | <button type="submit" class="btn btn-default">Verify</button> 44 | <div asp-validation-summary="ModelOnly" class="text-danger"></div> 45 | </form> 46 | </div> 47 | </div> 48 | </li> 49 | </ol> 50 | </div> 51 | 52 | @section Scripts { 53 | <partial name="_ValidationScriptsPartial" /> 54 | } 55 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FunApp.Data.Models; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 12 | { 13 | public class TwoFactorAuthenticationModel : PageModel 14 | { 15 | private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}"; 16 | 17 | private readonly UserManager<FunAppUser> _userManager; 18 | private readonly SignInManager<FunAppUser> _signInManager; 19 | private readonly ILogger<TwoFactorAuthenticationModel> _logger; 20 | 21 | public TwoFactorAuthenticationModel( 22 | UserManager<FunAppUser> userManager, 23 | SignInManager<FunAppUser> signInManager, 24 | ILogger<TwoFactorAuthenticationModel> logger) 25 | { 26 | _userManager = userManager; 27 | _signInManager = signInManager; 28 | _logger = logger; 29 | } 30 | 31 | public bool HasAuthenticator { get; set; } 32 | 33 | public int RecoveryCodesLeft { get; set; } 34 | 35 | [BindProperty] 36 | public bool Is2faEnabled { get; set; } 37 | 38 | public bool IsMachineRemembered { get; set; } 39 | 40 | [TempData] 41 | public string StatusMessage { get; set; } 42 | 43 | public async Task<IActionResult> OnGet() 44 | { 45 | var user = await _userManager.GetUserAsync(User); 46 | if (user == null) 47 | { 48 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 49 | } 50 | 51 | HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null; 52 | Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user); 53 | IsMachineRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user); 54 | RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user); 55 | 56 | return Page(); 57 | } 58 | 59 | public async Task<IActionResult> OnPost() 60 | { 61 | var user = await _userManager.GetUserAsync(User); 62 | if (user == null) 63 | { 64 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 65 | } 66 | 67 | await _signInManager.ForgetTwoFactorClientAsync(); 68 | StatusMessage = "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code."; 69 | return RedirectToPage(); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FunApp.Data.Models; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 12 | { 13 | public class GenerateRecoveryCodesModel : PageModel 14 | { 15 | private readonly UserManager<FunAppUser> _userManager; 16 | private readonly ILogger<GenerateRecoveryCodesModel> _logger; 17 | 18 | public GenerateRecoveryCodesModel( 19 | UserManager<FunAppUser> userManager, 20 | ILogger<GenerateRecoveryCodesModel> logger) 21 | { 22 | _userManager = userManager; 23 | _logger = logger; 24 | } 25 | 26 | [TempData] 27 | public string[] RecoveryCodes { get; set; } 28 | 29 | [TempData] 30 | public string StatusMessage { get; set; } 31 | 32 | public async Task<IActionResult> OnGetAsync() 33 | { 34 | var user = await _userManager.GetUserAsync(User); 35 | if (user == null) 36 | { 37 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 38 | } 39 | 40 | var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); 41 | if (!isTwoFactorEnabled) 42 | { 43 | var userId = await _userManager.GetUserIdAsync(user); 44 | throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' because they do not have 2FA enabled."); 45 | } 46 | 47 | return Page(); 48 | } 49 | 50 | public async Task<IActionResult> OnPostAsync() 51 | { 52 | var user = await _userManager.GetUserAsync(User); 53 | if (user == null) 54 | { 55 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 56 | } 57 | 58 | var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); 59 | var userId = await _userManager.GetUserIdAsync(user); 60 | if (!isTwoFactorEnabled) 61 | { 62 | throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' as they do not have 2FA enabled."); 63 | } 64 | 65 | var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); 66 | RecoveryCodes = recoveryCodes.ToArray(); 67 | 68 | _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); 69 | StatusMessage = "You have generated new recovery codes."; 70 | return RedirectToPage("./ShowRecoveryCodes"); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authorization; 7 | using FunApp.Data.Models; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.AspNetCore.Mvc.RazorPages; 11 | 12 | namespace FunApp.Web.Areas.Identity.Pages.Account 13 | { 14 | [AllowAnonymous] 15 | public class ResetPasswordModel : PageModel 16 | { 17 | private readonly UserManager<FunAppUser> _userManager; 18 | 19 | public ResetPasswordModel(UserManager<FunAppUser> userManager) 20 | { 21 | _userManager = userManager; 22 | } 23 | 24 | [BindProperty] 25 | public InputModel Input { get; set; } 26 | 27 | public class InputModel 28 | { 29 | [Required] 30 | [EmailAddress] 31 | public string Email { get; set; } 32 | 33 | [Required] 34 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 35 | [DataType(DataType.Password)] 36 | public string Password { get; set; } 37 | 38 | [DataType(DataType.Password)] 39 | [Display(Name = "Confirm password")] 40 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 41 | public string ConfirmPassword { get; set; } 42 | 43 | public string Code { get; set; } 44 | } 45 | 46 | public IActionResult OnGet(string code = null) 47 | { 48 | if (code == null) 49 | { 50 | return BadRequest("A code must be supplied for password reset."); 51 | } 52 | else 53 | { 54 | Input = new InputModel 55 | { 56 | Code = code 57 | }; 58 | return Page(); 59 | } 60 | } 61 | 62 | public async Task<IActionResult> OnPostAsync() 63 | { 64 | if (!ModelState.IsValid) 65 | { 66 | return Page(); 67 | } 68 | 69 | var user = await _userManager.FindByEmailAsync(Input.Email); 70 | if (user == null) 71 | { 72 | // Don't reveal that the user does not exist 73 | return RedirectToPage("./ResetPasswordConfirmation"); 74 | } 75 | 76 | var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password); 77 | if (result.Succeeded) 78 | { 79 | return RedirectToPage("./ResetPasswordConfirmation"); 80 | } 81 | 82 | foreach (var error in result.Errors) 83 | { 84 | ModelState.AddModelError(string.Empty, error.Description); 85 | } 86 | return Page(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Threading.Tasks; 4 | using FunApp.Data.Models; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 11 | { 12 | public class DeletePersonalDataModel : PageModel 13 | { 14 | private readonly UserManager<FunAppUser> _userManager; 15 | private readonly SignInManager<FunAppUser> _signInManager; 16 | private readonly ILogger<DeletePersonalDataModel> _logger; 17 | 18 | public DeletePersonalDataModel( 19 | UserManager<FunAppUser> userManager, 20 | SignInManager<FunAppUser> signInManager, 21 | ILogger<DeletePersonalDataModel> logger) 22 | { 23 | _userManager = userManager; 24 | _signInManager = signInManager; 25 | _logger = logger; 26 | } 27 | 28 | [BindProperty] 29 | public InputModel Input { get; set; } 30 | 31 | public class InputModel 32 | { 33 | [Required] 34 | [DataType(DataType.Password)] 35 | public string Password { get; set; } 36 | } 37 | 38 | public bool RequirePassword { get; set; } 39 | 40 | public async Task<IActionResult> OnGet() 41 | { 42 | var user = await _userManager.GetUserAsync(User); 43 | if (user == null) 44 | { 45 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 46 | } 47 | 48 | RequirePassword = await _userManager.HasPasswordAsync(user); 49 | return Page(); 50 | } 51 | 52 | public async Task<IActionResult> OnPostAsync() 53 | { 54 | var user = await _userManager.GetUserAsync(User); 55 | if (user == null) 56 | { 57 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 58 | } 59 | 60 | RequirePassword = await _userManager.HasPasswordAsync(user); 61 | if (RequirePassword) 62 | { 63 | if (!await _userManager.CheckPasswordAsync(user, Input.Password)) 64 | { 65 | ModelState.AddModelError(string.Empty, "Password not correct."); 66 | return Page(); 67 | } 68 | } 69 | 70 | var result = await _userManager.DeleteAsync(user); 71 | var userId = await _userManager.GetUserIdAsync(user); 72 | if (!result.Succeeded) 73 | { 74 | throw new InvalidOperationException($"Unexpected error occurred deleteing user with ID '{userId}'."); 75 | } 76 | 77 | await _signInManager.SignOutAsync(); 78 | 79 | _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); 80 | 81 | return Redirect("~/"); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /api/WebApiDemo/Controllers/CitiesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.EntityFrameworkCore; 9 | using WebApiDemo.Data; 10 | 11 | namespace WebApiDemo.Controllers 12 | { 13 | public class CitiesController : ApiController 14 | { 15 | private readonly MyDbContext context; 16 | 17 | public CitiesController(MyDbContext context) 18 | { 19 | this.context = context; 20 | } 21 | 22 | // GET api/values 23 | [HttpGet] 24 | public ActionResult<IEnumerable<CityInfo>> Get(int page) 25 | { 26 | return this.context.Cities.ToList(); 27 | } 28 | 29 | // GET api/cities/Sofia 30 | [HttpGet("{id}")] 31 | [ProducesDefaultResponseType] 32 | [ProducesResponseType(StatusCodes.Status200OK)] 33 | public ActionResult<CityInfo> Get(string id) 34 | { 35 | var city = this.context.Cities.FirstOrDefault(x => x.Name == id); 36 | if (city == null) 37 | { 38 | return this.NotFound(); 39 | } 40 | 41 | return city; 42 | } 43 | 44 | // POST api/values 45 | [HttpPost] 46 | [ProducesResponseType(StatusCodes.Status201Created)] 47 | [ProducesDefaultResponseType] 48 | public ActionResult<CityInfo> Post(CityInfo cityInfo) 49 | { 50 | this.context.Cities.Add(cityInfo); 51 | this.context.SaveChanges(); 52 | return this.CreatedAtAction(nameof(Get), new {id = cityInfo.Name}, cityInfo); 53 | } 54 | 55 | // PUT api/cities/Sofia 56 | [HttpPut("{id}")] 57 | [ProducesResponseType(StatusCodes.Status200OK)] 58 | [ProducesResponseType(StatusCodes.Status404NotFound)] 59 | [ProducesDefaultResponseType] 60 | public ActionResult<CityInfo> Put(string id, CityInfo cityInfo) 61 | { 62 | var dbId = this.context.Cities.Where(x => x.Name == id) 63 | .Select(x => x.Id).FirstOrDefault(); 64 | 65 | if (dbId == 0) 66 | { 67 | return this.NotFound(); 68 | } 69 | 70 | cityInfo.Id = dbId; 71 | this.context.Entry(cityInfo).State = EntityState.Modified; 72 | this.context.SaveChanges(); 73 | 74 | return cityInfo; 75 | } 76 | 77 | // DELETE api/cities/Sofia 78 | [HttpDelete("{id}")] 79 | [ProducesDefaultResponseType] 80 | [ProducesResponseType(StatusCodes.Status200OK)] 81 | [ProducesResponseType(StatusCodes.Status404NotFound)] 82 | public ActionResult<CityInfo> Delete(string id) 83 | { 84 | var city = this.context.Cities.FirstOrDefault(x => x.Name == id); 85 | if (city == null) 86 | { 87 | return this.NotFound(); 88 | } 89 | 90 | this.context.Cities.Remove(city); 91 | this.context.SaveChanges(); 92 | 93 | return city; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Controllers/JokesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FunApp.Services.DataServices; 6 | using FunApp.Services.MachineLearning; 7 | using FunApp.Services.Models.Jokes; 8 | using FunApp.Web.Model.Jokes; 9 | using Microsoft.AspNetCore.Authorization; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.AspNetCore.Mvc.Rendering; 12 | using Newtonsoft.Json; 13 | 14 | namespace FunApp.Web.Controllers 15 | { 16 | public class JokesController : BaseController 17 | { 18 | private readonly IJokesService jokesService; 19 | private readonly ICategoriesService categoriesService; 20 | private readonly IJokesCategorizer jokesCategorizer; 21 | 22 | public JokesController( 23 | IJokesService jokesService, 24 | ICategoriesService categoriesService, 25 | IJokesCategorizer jokesCategorizer) 26 | { 27 | this.jokesService = jokesService; 28 | this.categoriesService = categoriesService; 29 | this.jokesCategorizer = jokesCategorizer; 30 | } 31 | 32 | [Authorize] 33 | public IActionResult Create() 34 | { 35 | this.ViewData["Categories"] = this.categoriesService.GetAll() 36 | .Select(x => new SelectListItem 37 | { 38 | Value = x.Id.ToString(), 39 | Text = x.NameAndCount, 40 | }); 41 | return this.View(); 42 | } 43 | 44 | [HttpPost] 45 | public async Task<IActionResult> Create(CreateJokeInputModel input) 46 | { 47 | if (!this.ModelState.IsValid) 48 | { 49 | return this.View(input); 50 | } 51 | 52 | var id = await this.jokesService.Create(input.CategoryId, input.Content); 53 | return this.RedirectToAction("Details", new { id = id }); 54 | } 55 | 56 | public IActionResult Details(int id) 57 | { 58 | var joke = this.jokesService.GetJokeById<JokeDetailsViewModel>(id); 59 | return this.View(joke); 60 | } 61 | 62 | [HttpPost] 63 | public SuggestCategoryResult SuggestCategory(string joke) 64 | { 65 | var category = this.jokesCategorizer.Categorize("MlModels/JokesCategoryModel.zip", joke); 66 | var categoryId = this.categoriesService.GetCategoryId(category); 67 | return new SuggestCategoryResult { CategoryId = categoryId ?? 0, CategoryName = category }; 68 | } 69 | 70 | [HttpPost] 71 | public object RateJoke(int jokeId, int rating) 72 | { 73 | var rateJoke = this.jokesService.AddRatingToJoke(jokeId, rating); 74 | if (!rateJoke) 75 | { 76 | return Json($"An error occurred while processing your vote"); 77 | } 78 | var successMessage = $"You successfully rated the joke with rating of {rating}"; 79 | return Json(successMessage); 80 | } 81 | } 82 | 83 | public class SuggestCategoryResult 84 | { 85 | public int CategoryId { get; set; } 86 | 87 | public string CategoryName { get; set; } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /api/WebApiDemos.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "a6899215-224b-4ceb-b1f6-1d638d509546", 4 | "name": "WebApiDemos", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "FunApp Index", 10 | "request": { 11 | "method": "GET", 12 | "header": [], 13 | "body": { 14 | "mode": "raw", 15 | "raw": "" 16 | }, 17 | "url": { 18 | "raw": "" 19 | } 20 | }, 21 | "response": [] 22 | }, 23 | { 24 | "name": "GET /api/values", 25 | "request": { 26 | "method": "GET", 27 | "header": [], 28 | "body": { 29 | "mode": "raw", 30 | "raw": "" 31 | }, 32 | "url": { 33 | "raw": "https://localhost:44390/api/Cities/", 34 | "protocol": "https", 35 | "host": [ 36 | "localhost" 37 | ], 38 | "port": "44390", 39 | "path": [ 40 | "api", 41 | "Cities", 42 | "" 43 | ] 44 | } 45 | }, 46 | "response": [] 47 | }, 48 | { 49 | "name": "POST /api/values", 50 | "request": { 51 | "method": "POST", 52 | "header": [ 53 | { 54 | "key": "Content-Type", 55 | "name": "Content-Type", 56 | "value": "application/json", 57 | "type": "text" 58 | } 59 | ], 60 | "body": { 61 | "mode": "raw", 62 | "raw": "{\n \"name\": \"Bo\",\n \"population\": 0,\n \"temperature\": -600\n}" 63 | }, 64 | "url": { 65 | "raw": "https://localhost:44390/api/cities/", 66 | "protocol": "https", 67 | "host": [ 68 | "localhost" 69 | ], 70 | "port": "44390", 71 | "path": [ 72 | "api", 73 | "cities", 74 | "" 75 | ] 76 | } 77 | }, 78 | "response": [] 79 | }, 80 | { 81 | "name": "PUT /api/values", 82 | "request": { 83 | "method": "PUT", 84 | "header": [ 85 | { 86 | "key": "Content-Type", 87 | "value": "application/json", 88 | "type": "text" 89 | } 90 | ], 91 | "body": { 92 | "mode": "raw", 93 | "raw": " {\n \"name\": \"Sofia\",\n \"population\": 2500000,\n \"temperature\": 3\n }" 94 | }, 95 | "url": { 96 | "raw": "https://localhost:44390/api/cities/Sofia", 97 | "protocol": "https", 98 | "host": [ 99 | "localhost" 100 | ], 101 | "port": "44390", 102 | "path": [ 103 | "api", 104 | "cities", 105 | "Sofia" 106 | ] 107 | } 108 | }, 109 | "response": [] 110 | }, 111 | { 112 | "name": "DELETE /api/values", 113 | "request": { 114 | "method": "DELETE", 115 | "header": [ 116 | { 117 | "key": "Content-Type", 118 | "type": "text", 119 | "value": "application/json", 120 | "disabled": true 121 | } 122 | ], 123 | "body": { 124 | "mode": "raw", 125 | "raw": "" 126 | }, 127 | "url": { 128 | "raw": "https://localhost:44390/api/cities/Plovdiv", 129 | "protocol": "https", 130 | "host": [ 131 | "localhost" 132 | ], 133 | "port": "44390", 134 | "path": [ 135 | "api", 136 | "cities", 137 | "Plovdiv" 138 | ] 139 | } 140 | }, 141 | "response": [] 142 | } 143 | ] 144 | } -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using FunApp.Data.Models; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | 11 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 12 | { 13 | public class SetPasswordModel : PageModel 14 | { 15 | private readonly UserManager<FunAppUser> _userManager; 16 | private readonly SignInManager<FunAppUser> _signInManager; 17 | 18 | public SetPasswordModel( 19 | UserManager<FunAppUser> userManager, 20 | SignInManager<FunAppUser> signInManager) 21 | { 22 | _userManager = userManager; 23 | _signInManager = signInManager; 24 | } 25 | 26 | [BindProperty] 27 | public InputModel Input { get; set; } 28 | 29 | [TempData] 30 | public string StatusMessage { get; set; } 31 | 32 | public class InputModel 33 | { 34 | [Required] 35 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 36 | [DataType(DataType.Password)] 37 | [Display(Name = "New password")] 38 | public string NewPassword { get; set; } 39 | 40 | [DataType(DataType.Password)] 41 | [Display(Name = "Confirm new password")] 42 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 43 | public string ConfirmPassword { get; set; } 44 | } 45 | 46 | public async Task<IActionResult> OnGetAsync() 47 | { 48 | var user = await _userManager.GetUserAsync(User); 49 | if (user == null) 50 | { 51 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 52 | } 53 | 54 | var hasPassword = await _userManager.HasPasswordAsync(user); 55 | 56 | if (hasPassword) 57 | { 58 | return RedirectToPage("./ChangePassword"); 59 | } 60 | 61 | return Page(); 62 | } 63 | 64 | public async Task<IActionResult> OnPostAsync() 65 | { 66 | if (!ModelState.IsValid) 67 | { 68 | return Page(); 69 | } 70 | 71 | var user = await _userManager.GetUserAsync(User); 72 | if (user == null) 73 | { 74 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 75 | } 76 | 77 | var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword); 78 | if (!addPasswordResult.Succeeded) 79 | { 80 | foreach (var error in addPasswordResult.Errors) 81 | { 82 | ModelState.AddModelError(string.Empty, error.Description); 83 | } 84 | return Page(); 85 | } 86 | 87 | await _signInManager.RefreshSignInAsync(user); 88 | StatusMessage = "Your password has been set."; 89 | 90 | return RedirectToPage(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authorization; 7 | using FunApp.Data.Models; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.AspNetCore.Mvc.RazorPages; 11 | using Microsoft.Extensions.Logging; 12 | 13 | namespace FunApp.Web.Areas.Identity.Pages.Account 14 | { 15 | [AllowAnonymous] 16 | public class LoginWithRecoveryCodeModel : PageModel 17 | { 18 | private readonly SignInManager<FunAppUser> _signInManager; 19 | private readonly ILogger<LoginWithRecoveryCodeModel> _logger; 20 | 21 | public LoginWithRecoveryCodeModel(SignInManager<FunAppUser> signInManager, ILogger<LoginWithRecoveryCodeModel> logger) 22 | { 23 | _signInManager = signInManager; 24 | _logger = logger; 25 | } 26 | 27 | [BindProperty] 28 | public InputModel Input { get; set; } 29 | 30 | public string ReturnUrl { get; set; } 31 | 32 | public class InputModel 33 | { 34 | [BindProperty] 35 | [Required] 36 | [DataType(DataType.Text)] 37 | [Display(Name = "Recovery Code")] 38 | public string RecoveryCode { get; set; } 39 | } 40 | 41 | public async Task<IActionResult> OnGetAsync(string returnUrl = null) 42 | { 43 | // Ensure the user has gone through the username & password screen first 44 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 45 | if (user == null) 46 | { 47 | throw new InvalidOperationException($"Unable to load two-factor authentication user."); 48 | } 49 | 50 | ReturnUrl = returnUrl; 51 | 52 | return Page(); 53 | } 54 | 55 | public async Task<IActionResult> OnPostAsync(string returnUrl = null) 56 | { 57 | if (!ModelState.IsValid) 58 | { 59 | return Page(); 60 | } 61 | 62 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 63 | if (user == null) 64 | { 65 | throw new InvalidOperationException($"Unable to load two-factor authentication user."); 66 | } 67 | 68 | var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty); 69 | 70 | var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); 71 | 72 | if (result.Succeeded) 73 | { 74 | _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id); 75 | return LocalRedirect(returnUrl ?? Url.Content("~/")); 76 | } 77 | if (result.IsLockedOut) 78 | { 79 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id); 80 | return RedirectToPage("./Lockout"); 81 | } 82 | else 83 | { 84 | _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id); 85 | ModelState.AddModelError(string.Empty, "Invalid recovery code entered."); 86 | return Page(); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginModel 3 | 4 | @{ 5 | ViewData["Title"] = "Log in"; 6 | } 7 | 8 | <h2>@ViewData["Title"]</h2> 9 | <div class="row"> 10 | <div class="col-md-4"> 11 | <section> 12 | <form method="post"> 13 | <h4>Use a local account to log in.</h4> 14 | <hr /> 15 | <div asp-validation-summary="All" class="text-danger"></div> 16 | <div class="form-group"> 17 | <label asp-for="Input.Email"></label> 18 | <input asp-for="Input.Email" class="form-control" /> 19 | <span asp-validation-for="Input.Email" class="text-danger"></span> 20 | </div> 21 | <div class="form-group"> 22 | <label asp-for="Input.Password"></label> 23 | <input asp-for="Input.Password" class="form-control" /> 24 | <span asp-validation-for="Input.Password" class="text-danger"></span> 25 | </div> 26 | <div class="form-group"> 27 | <div class="checkbox"> 28 | <label asp-for="Input.RememberMe"> 29 | <input asp-for="Input.RememberMe" /> 30 | @Html.DisplayNameFor(m => m.Input.RememberMe) 31 | </label> 32 | </div> 33 | </div> 34 | <div class="form-group"> 35 | <button type="submit" class="btn btn-default">Log in</button> 36 | </div> 37 | <div class="form-group"> 38 | <p> 39 | <a asp-page="./ForgotPassword">Forgot your password?</a> 40 | </p> 41 | <p> 42 | <a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a> 43 | </p> 44 | </div> 45 | </form> 46 | </section> 47 | </div> 48 | <div class="col-md-6 col-md-offset-2"> 49 | <section> 50 | <h4>Use another service to log in.</h4> 51 | <hr /> 52 | @{ 53 | if ((Model.ExternalLogins?.Count ?? 0) == 0) 54 | { 55 | <div> 56 | <p> 57 | There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a> 58 | for details on setting up this ASP.NET application to support logging in via external services. 59 | </p> 60 | </div> 61 | } 62 | else 63 | { 64 | <form asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal"> 65 | <div> 66 | <p> 67 | @foreach (var provider in Model.ExternalLogins) 68 | { 69 | <button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> 70 | } 71 | </p> 72 | </div> 73 | </form> 74 | } 75 | } 76 | </section> 77 | </div> 78 | </div> 79 | 80 | @section Scripts { 81 | <partial name="_ValidationScriptsPartial" /> 82 | } 83 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/FunApp.Web.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk.Web"> 2 | 3 | <PropertyGroup> 4 | <TargetFramework>netcoreapp2.2</TargetFramework> 5 | <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> 6 | <UserSecretsId>aspnet-FunApp.Web-44D7697C-476D-4D10-8AAC-D8C4F4FD8B68</UserSecretsId> 7 | <ApplicationInsightsResourceId>/subscriptions/9c5540eb-59c5-47d8-9812-00f7249e9502/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/FunApp.Web</ApplicationInsightsResourceId> 8 | <ApplicationInsightsAnnotationResourceId>/subscriptions/9c5540eb-59c5-47d8-9812-00f7249e9502/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/FunApp.Web</ApplicationInsightsAnnotationResourceId> 9 | </PropertyGroup> 10 | 11 | <ItemGroup> 12 | <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="6.0.0" /> 13 | <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.5.1" /> 14 | <PackageReference Include="Microsoft.AspNetCore.App" /> 15 | <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" /> 16 | <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.0" PrivateAssets="All" /> 17 | <PackageReference Include="X.PagedList.Mvc.Core" Version="7.6.0" /> 18 | </ItemGroup> 19 | 20 | <ItemGroup> 21 | <ProjectReference Include="..\..\Data\FunApp.Data.Models\FunApp.Data.Models.csproj" /> 22 | <ProjectReference Include="..\..\Data\FunApp.Data\FunApp.Data.csproj" /> 23 | <ProjectReference Include="..\..\Services\FunApp.Services.DataServices\FunApp.Services.DataServices.csproj" /> 24 | <ProjectReference Include="..\..\Services\FunApp.Services.MachineLearning\FunApp.Services.MachineLearning.csproj" /> 25 | <ProjectReference Include="..\..\Services\FunApp.Services.Mapping\FunApp.Services.Mapping.csproj" /> 26 | <ProjectReference Include="..\..\Services\FunApp.Services.Models\FunApp.Services.Models.csproj" /> 27 | <ProjectReference Include="..\FunApp.Web.Forum\FunApp.Web.Forum.csproj" /> 28 | </ItemGroup> 29 | 30 | <ItemGroup> 31 | <None Update="MlModels\JokesCategoryModel.zip"> 32 | <CopyToOutputDirectory>Always</CopyToOutputDirectory> 33 | </None> 34 | </ItemGroup> 35 | 36 | <ItemGroup> 37 | <Folder Include="Areas\Administration\Data\" /> 38 | </ItemGroup> 39 | 40 | <ItemGroup> 41 | <Content Update="Areas\Administration\Pages\_ViewImports.cshtml"> 42 | <Pack>$(IncludeRazorContentInPack)</Pack> 43 | </Content> 44 | <Content Update="Areas\Administration\Pages\_ViewStart.cshtml"> 45 | <Pack>$(IncludeRazorContentInPack)</Pack> 46 | </Content> 47 | <Content Update="Areas\Administration\Views\Categories\Create.cshtml"> 48 | <Pack>$(IncludeRazorContentInPack)</Pack> 49 | </Content> 50 | <Content Update="Areas\Administration\Views\Categories\Edit.cshtml"> 51 | <Pack>$(IncludeRazorContentInPack)</Pack> 52 | </Content> 53 | <Content Update="Areas\Administration\Views\Categories\Delete.cshtml"> 54 | <Pack>$(IncludeRazorContentInPack)</Pack> 55 | </Content> 56 | <Content Update="Areas\Administration\Views\_ViewImports.cshtml"> 57 | <Pack>$(IncludeRazorContentInPack)</Pack> 58 | </Content> 59 | <Content Update="Areas\Administration\Views\_ViewStart.cshtml"> 60 | <Pack>$(IncludeRazorContentInPack)</Pack> 61 | </Content> 62 | <Content Update="Areas\MyFeature\Pages\_ViewStart.cshtml"> 63 | <Pack>$(IncludeRazorContentInPack)</Pack> 64 | </Content> 65 | </ItemGroup> 66 | 67 | <ItemGroup> 68 | <WCFMetadata Include="Connected Services" /> 69 | </ItemGroup> 70 | 71 | </Project> 72 | -------------------------------------------------------------------------------- /src/Services/FunApp.Services.Mapping/AutoMapperConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using AutoMapper; 6 | 7 | namespace FunApp.Services.Mapping 8 | { 9 | public static class AutoMapperConfig 10 | { 11 | private static bool initialized; 12 | 13 | public static void RegisterMappings(params Assembly[] assemblies) 14 | { 15 | if (initialized) 16 | { 17 | return; 18 | } 19 | 20 | initialized = true; 21 | 22 | var types = assemblies.SelectMany(a => a.GetExportedTypes()).ToList(); 23 | Mapper.Initialize(configuration => 24 | { 25 | // IMapFrom<> 26 | foreach (var map in GetFromMaps(types)) 27 | { 28 | configuration.CreateMap(map.Source, map.Destination); 29 | } 30 | 31 | // IMapTo<> 32 | foreach (var map in GetToMaps(types)) 33 | { 34 | configuration.CreateMap(map.Source, map.Destination); 35 | } 36 | 37 | // IHaveCustomMappings 38 | foreach (var map in GetCustomMappings(types)) 39 | { 40 | map.CreateMappings(configuration); 41 | } 42 | }); 43 | } 44 | private static IEnumerable<TypesMap> GetFromMaps( 45 | IEnumerable<Type> types) 46 | { 47 | var fromMaps = from t in types 48 | from i in t.GetTypeInfo().GetInterfaces() 49 | where i.GetTypeInfo().IsGenericType && 50 | i.GetGenericTypeDefinition() == typeof(IMapFrom<>) && 51 | !t.GetTypeInfo().IsAbstract && 52 | !t.GetTypeInfo().IsInterface 53 | select new TypesMap 54 | { 55 | Source = i.GetTypeInfo().GetGenericArguments()[0], 56 | Destination = t, 57 | }; 58 | 59 | return fromMaps; 60 | } 61 | 62 | private static IEnumerable<TypesMap> GetToMaps(IEnumerable<Type> types) 63 | { 64 | var toMaps = from t in types 65 | from i in t.GetTypeInfo().GetInterfaces() 66 | where i.GetTypeInfo().IsGenericType && 67 | i.GetTypeInfo().GetGenericTypeDefinition() == typeof(IMapTo<>) && 68 | !t.GetTypeInfo().IsAbstract && 69 | !t.GetTypeInfo().IsInterface 70 | select new TypesMap 71 | { 72 | Source = t, 73 | Destination = i.GetTypeInfo().GetGenericArguments()[0], 74 | }; 75 | 76 | return toMaps; 77 | } 78 | 79 | private static IEnumerable<IHaveCustomMappings> GetCustomMappings(IEnumerable<Type> types) 80 | { 81 | var customMaps = from t in types 82 | from i in t.GetTypeInfo().GetInterfaces() 83 | where typeof(IHaveCustomMappings).GetTypeInfo().IsAssignableFrom(t) && 84 | !t.GetTypeInfo().IsAbstract && 85 | !t.GetTypeInfo().IsInterface 86 | select (IHaveCustomMappings)Activator.CreateInstance(t); 87 | 88 | return customMaps; 89 | } 90 | 91 | private class TypesMap 92 | { 93 | public Type Source { get; set; } 94 | 95 | public Type Destination { get; set; } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authorization; 7 | using FunApp.Data.Models; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.AspNetCore.Mvc.RazorPages; 11 | using Microsoft.Extensions.Logging; 12 | 13 | namespace FunApp.Web.Areas.Identity.Pages.Account 14 | { 15 | [AllowAnonymous] 16 | public class LoginWith2faModel : PageModel 17 | { 18 | private readonly SignInManager<FunAppUser> _signInManager; 19 | private readonly ILogger<LoginWith2faModel> _logger; 20 | 21 | public LoginWith2faModel(SignInManager<FunAppUser> signInManager, ILogger<LoginWith2faModel> logger) 22 | { 23 | _signInManager = signInManager; 24 | _logger = logger; 25 | } 26 | 27 | [BindProperty] 28 | public InputModel Input { get; set; } 29 | 30 | public bool RememberMe { get; set; } 31 | 32 | public string ReturnUrl { get; set; } 33 | 34 | public class InputModel 35 | { 36 | [Required] 37 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 38 | [DataType(DataType.Text)] 39 | [Display(Name = "Authenticator code")] 40 | public string TwoFactorCode { get; set; } 41 | 42 | [Display(Name = "Remember this machine")] 43 | public bool RememberMachine { get; set; } 44 | } 45 | 46 | public async Task<IActionResult> OnGetAsync(bool rememberMe, string returnUrl = null) 47 | { 48 | // Ensure the user has gone through the username & password screen first 49 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 50 | 51 | if (user == null) 52 | { 53 | throw new InvalidOperationException($"Unable to load two-factor authentication user."); 54 | } 55 | 56 | ReturnUrl = returnUrl; 57 | RememberMe = rememberMe; 58 | 59 | return Page(); 60 | } 61 | 62 | public async Task<IActionResult> OnPostAsync(bool rememberMe, string returnUrl = null) 63 | { 64 | if (!ModelState.IsValid) 65 | { 66 | return Page(); 67 | } 68 | 69 | returnUrl = returnUrl ?? Url.Content("~/"); 70 | 71 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 72 | if (user == null) 73 | { 74 | throw new InvalidOperationException($"Unable to load two-factor authentication user."); 75 | } 76 | 77 | var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty); 78 | 79 | var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine); 80 | 81 | if (result.Succeeded) 82 | { 83 | _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id); 84 | return LocalRedirect(returnUrl); 85 | } 86 | else if (result.IsLockedOut) 87 | { 88 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id); 89 | return RedirectToPage("./Lockout"); 90 | } 91 | else 92 | { 93 | _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id); 94 | ModelState.AddModelError(string.Empty, "Invalid authenticator code."); 95 | return Page(); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using FunApp.Data.Models; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using Microsoft.Extensions.Logging; 11 | namespace FunApp.Web.Areas.Identity.Pages.Account.Manage 12 | { 13 | public class ChangePasswordModel : PageModel 14 | { 15 | private readonly UserManager<FunAppUser> _userManager; 16 | private readonly SignInManager<FunAppUser> _signInManager; 17 | private readonly ILogger<ChangePasswordModel> _logger; 18 | 19 | public ChangePasswordModel( 20 | UserManager<FunAppUser> userManager, 21 | SignInManager<FunAppUser> signInManager, 22 | ILogger<ChangePasswordModel> logger) 23 | { 24 | _userManager = userManager; 25 | _signInManager = signInManager; 26 | _logger = logger; 27 | } 28 | 29 | [BindProperty] 30 | public InputModel Input { get; set; } 31 | 32 | [TempData] 33 | public string StatusMessage { get; set; } 34 | 35 | public class InputModel 36 | { 37 | [Required] 38 | [DataType(DataType.Password)] 39 | [Display(Name = "Current password")] 40 | public string OldPassword { get; set; } 41 | 42 | [Required] 43 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 44 | [DataType(DataType.Password)] 45 | [Display(Name = "New password")] 46 | public string NewPassword { get; set; } 47 | 48 | [DataType(DataType.Password)] 49 | [Display(Name = "Confirm new password")] 50 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 51 | public string ConfirmPassword { get; set; } 52 | } 53 | 54 | public async Task<IActionResult> OnGetAsync() 55 | { 56 | var user = await _userManager.GetUserAsync(User); 57 | if (user == null) 58 | { 59 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 60 | } 61 | 62 | var hasPassword = await _userManager.HasPasswordAsync(user); 63 | if (!hasPassword) 64 | { 65 | return RedirectToPage("./SetPassword"); 66 | } 67 | 68 | return Page(); 69 | } 70 | 71 | public async Task<IActionResult> OnPostAsync() 72 | { 73 | if (!ModelState.IsValid) 74 | { 75 | return Page(); 76 | } 77 | 78 | var user = await _userManager.GetUserAsync(User); 79 | if (user == null) 80 | { 81 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 82 | } 83 | 84 | var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); 85 | if (!changePasswordResult.Succeeded) 86 | { 87 | foreach (var error in changePasswordResult.Errors) 88 | { 89 | ModelState.AddModelError(string.Empty, error.Description); 90 | } 91 | return Page(); 92 | } 93 | 94 | await _signInManager.RefreshSignInAsync(user); 95 | _logger.LogInformation("User changed their password successfully."); 96 | StatusMessage = "Your password has been changed."; 97 | 98 | return RedirectToPage(); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Login.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authorization; 7 | using FunApp.Data.Models; 8 | using Microsoft.AspNetCore.Authentication; 9 | using Microsoft.AspNetCore.Identity; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.AspNetCore.Mvc.RazorPages; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace FunApp.Web.Areas.Identity.Pages.Account 15 | { 16 | [AllowAnonymous] 17 | public class LoginModel : PageModel 18 | { 19 | private readonly SignInManager<FunAppUser> _signInManager; 20 | private readonly ILogger<LoginModel> _logger; 21 | 22 | public LoginModel(SignInManager<FunAppUser> signInManager, ILogger<LoginModel> logger) 23 | { 24 | _signInManager = signInManager; 25 | _logger = logger; 26 | } 27 | 28 | [BindProperty] 29 | public InputModel Input { get; set; } 30 | 31 | public IList<AuthenticationScheme> ExternalLogins { get; set; } 32 | 33 | public string ReturnUrl { get; set; } 34 | 35 | [TempData] 36 | public string ErrorMessage { get; set; } 37 | 38 | public class InputModel 39 | { 40 | [Required] 41 | [EmailAddress] 42 | public string Email { get; set; } 43 | 44 | [Required] 45 | [DataType(DataType.Password)] 46 | public string Password { get; set; } 47 | 48 | [Display(Name = "Remember me?")] 49 | public bool RememberMe { get; set; } 50 | } 51 | 52 | public async Task OnGetAsync(string returnUrl = null) 53 | { 54 | if (!string.IsNullOrEmpty(ErrorMessage)) 55 | { 56 | ModelState.AddModelError(string.Empty, ErrorMessage); 57 | } 58 | 59 | returnUrl = returnUrl ?? Url.Content("~/"); 60 | 61 | // Clear the existing external cookie to ensure a clean login process 62 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); 63 | 64 | ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); 65 | 66 | ReturnUrl = returnUrl; 67 | } 68 | 69 | public async Task<IActionResult> OnPostAsync(string returnUrl = null) 70 | { 71 | returnUrl = returnUrl ?? Url.Content("~/"); 72 | 73 | if (ModelState.IsValid) 74 | { 75 | // This doesn't count login failures towards account lockout 76 | // To enable password failures to trigger account lockout, set lockoutOnFailure: true 77 | var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true); 78 | if (result.Succeeded) 79 | { 80 | _logger.LogInformation("User logged in."); 81 | return LocalRedirect(returnUrl); 82 | } 83 | if (result.RequiresTwoFactor) 84 | { 85 | return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe }); 86 | } 87 | if (result.IsLockedOut) 88 | { 89 | _logger.LogWarning("User account locked out."); 90 | return RedirectToPage("./Lockout"); 91 | } 92 | else 93 | { 94 | ModelState.AddModelError(string.Empty, "Invalid login attempt."); 95 | return Page(); 96 | } 97 | } 98 | 99 | // If we got this far, something failed, redisplay form 100 | return Page(); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Areas/Identity/Pages/Account/Register.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Encodings.Web; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authorization; 7 | using FunApp.Data.Models; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.AspNetCore.Identity.UI.Services; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.AspNetCore.Mvc.RazorPages; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace FunApp.Web.Areas.Identity.Pages.Account 15 | { 16 | [AllowAnonymous] 17 | public class RegisterModel : PageModel 18 | { 19 | private readonly SignInManager<FunAppUser> _signInManager; 20 | private readonly UserManager<FunAppUser> _userManager; 21 | private readonly ILogger<RegisterModel> _logger; 22 | private readonly IEmailSender _emailSender; 23 | 24 | public RegisterModel( 25 | UserManager<FunAppUser> userManager, 26 | SignInManager<FunAppUser> signInManager, 27 | ILogger<RegisterModel> logger, 28 | IEmailSender emailSender) 29 | { 30 | _userManager = userManager; 31 | _signInManager = signInManager; 32 | _logger = logger; 33 | _emailSender = emailSender; 34 | } 35 | 36 | [BindProperty] 37 | public InputModel Input { get; set; } 38 | 39 | public string ReturnUrl { get; set; } 40 | 41 | public class InputModel 42 | { 43 | [Required] 44 | [EmailAddress] 45 | [Display(Name = "Email")] 46 | public string Email { get; set; } 47 | 48 | [Required] 49 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 50 | [DataType(DataType.Password)] 51 | [Display(Name = "Password")] 52 | public string Password { get; set; } 53 | 54 | [DataType(DataType.Password)] 55 | [Display(Name = "Confirm password")] 56 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 57 | public string ConfirmPassword { get; set; } 58 | } 59 | 60 | public void OnGet(string returnUrl = null) 61 | { 62 | ReturnUrl = returnUrl; 63 | } 64 | 65 | public async Task<IActionResult> OnPostAsync(string returnUrl = null) 66 | { 67 | returnUrl = returnUrl ?? Url.Content("~/"); 68 | if (ModelState.IsValid) 69 | { 70 | var user = new FunAppUser { UserName = Input.Email, Email = Input.Email }; 71 | var result = await _userManager.CreateAsync(user, Input.Password); 72 | if (result.Succeeded) 73 | { 74 | _logger.LogInformation("User created a new account with password."); 75 | 76 | var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); 77 | var callbackUrl = Url.Page( 78 | "/Account/ConfirmEmail", 79 | pageHandler: null, 80 | values: new { userId = user.Id, code = code }, 81 | protocol: Request.Scheme); 82 | 83 | await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", 84 | $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); 85 | 86 | await _signInManager.SignInAsync(user, isPersistent: false); 87 | return LocalRedirect(returnUrl); 88 | } 89 | foreach (var error in result.Errors) 90 | { 91 | ModelState.AddModelError(string.Empty, error.Description); 92 | } 93 | } 94 | 95 | // If we got this far, something failed, redisplay form 96 | return Page(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Web/FunApp.Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet 2 | <!DOCTYPE html> 3 | <html> 4 | <head> 5 | <meta charset="utf-8" /> 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 | <title>@ViewData["Title"] - FunApp.Web 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 38 | 39 | 40 | @Html.Raw(JavaScriptSnippet.FullScript) 41 | 42 | 43 | 69 | 70 | 71 | 72 |
73 | @RenderBody() 74 |
75 |
76 |

© 2018 - FunApp.Web

77 |
78 |
79 | 80 | @RenderSection("Scripts", required: false) 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/Tests/Sandbox/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.IO; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using System.Threading; 9 | using AngleSharp; 10 | using AngleSharp.Parser.Html; 11 | using CommandLine; 12 | using FunApp.Data; 13 | using FunApp.Data.Common; 14 | using FunApp.Data.Models; 15 | using FunApp.Web.Models; 16 | using Microsoft.EntityFrameworkCore; 17 | using Microsoft.Extensions.Configuration; 18 | using Microsoft.Extensions.DependencyInjection; 19 | 20 | namespace Sandbox 21 | { 22 | public static class Program 23 | { 24 | public static void Main(string[] args) 25 | { 26 | Console.OutputEncoding = Encoding.UTF8; 27 | Console.WriteLine($"{typeof(Program).Namespace} ({string.Join(" ", args)}) starts working..."); 28 | var serviceCollection = new ServiceCollection(); 29 | ConfigureServices(serviceCollection); 30 | IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(true); 31 | 32 | using (var serviceScope = serviceProvider.CreateScope()) 33 | { 34 | serviceProvider = serviceScope.ServiceProvider; 35 | SandboxCode(serviceProvider); 36 | } 37 | } 38 | 39 | private static void SandboxCode(IServiceProvider serviceProvider) 40 | { 41 | var context = serviceProvider.GetService(); 42 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 43 | var parser = new HtmlParser(); 44 | var webClient = new WebClient {Encoding = Encoding.GetEncoding("windows-1251") }; 45 | 46 | for (var i = 4233; i < 10000; i++) 47 | { 48 | var url = "http://fun.dir.bg/vic_open.php?id=" + i; 49 | string html = null; 50 | for (int j = 0; j < 10; j++) 51 | { 52 | try 53 | { 54 | html = webClient.DownloadString(url); 55 | break; 56 | } 57 | catch (Exception) 58 | { 59 | Thread.Sleep(10000); 60 | } 61 | } 62 | 63 | if (string.IsNullOrWhiteSpace(html)) 64 | { 65 | continue; 66 | } 67 | 68 | var document = parser.Parse(html); 69 | var jokeContent = document.QuerySelector("#newsbody")?.TextContent?.Trim(); 70 | var categoryName = document.QuerySelector(".tag-links-left a")?.TextContent?.Trim(); 71 | 72 | if (!string.IsNullOrWhiteSpace(jokeContent) && 73 | !string.IsNullOrWhiteSpace(categoryName)) 74 | { 75 | var category = context.Categories.FirstOrDefault(x => x.Name == categoryName); 76 | if (category == null) 77 | { 78 | category = new Category 79 | { 80 | Name = categoryName, 81 | }; 82 | } 83 | 84 | var joke = new Joke() 85 | { 86 | Category = category, 87 | Content = jokeContent, 88 | }; 89 | 90 | context.Jokes.Add(joke); 91 | context.SaveChanges(); 92 | } 93 | 94 | Console.WriteLine($"{i} => {categoryName}"); 95 | } 96 | } 97 | 98 | private static void ConfigureServices(ServiceCollection services) 99 | { 100 | var configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()) 101 | .AddJsonFile("appsettings.json", false, true) 102 | .AddEnvironmentVariables() 103 | .Build(); 104 | 105 | services.AddDbContext(options => 106 | options.UseSqlServer( 107 | configuration.GetConnectionString("DefaultConnection"))); 108 | 109 | services.AddScoped(typeof(IRepository<>), typeof(DbRepository<>)); 110 | } 111 | } 112 | } 113 | --------------------------------------------------------------------------------