├── BoilerPlate.Web ├── Views │ ├── _ViewStart.cshtml │ ├── Home │ │ ├── Privacy.cshtml │ │ └── Index.cshtml │ ├── Shared │ │ ├── Components │ │ │ └── Home │ │ │ │ ├── Open.cshtml │ │ │ │ ├── ClosePreSeason.cshtml │ │ │ │ └── ClosedForSeason.cshtml │ │ ├── _ValidationScriptsPartial.cshtml │ │ ├── Footer.cshtml │ │ ├── Head.cshtml │ │ ├── GTM.cshtml │ │ ├── Error.cshtml │ │ ├── _Layout.cshtml │ │ └── _AdminLayout.cshtml │ ├── _ViewImports.cshtml │ ├── Account │ │ ├── SignIn.cshtml │ │ └── SignUp.cshtml │ ├── Admin │ │ └── SignIn.cshtml │ └── Settings │ │ └── Create.cshtml ├── wwwroot │ ├── favicon.ico │ ├── js │ │ └── site.js │ └── css │ │ └── site.css ├── appsettings.dev.Development.json ├── appsettings.production.Development.json ├── appsettings.dev.json ├── appsettings.staging.json ├── appsettings.production.json ├── appsettings.json ├── Dockerfile ├── Dockerfile.original ├── Program-old.cs ├── Controllers │ ├── HomeController.cs │ ├── AdminController.cs │ ├── ViewComponents │ │ └── HomeViewComponent.cs │ ├── SettingsController.cs │ ├── OrderController.cs │ └── BaseController.cs ├── BoilerPlate.Web.csproj └── Properties │ └── launchSettings.json ├── BoilerPlate.Service.Entities ├── Class1.cs └── BoilerPlate.Service.Entities.csproj ├── BoilerPlate.Web.Entities ├── ViewModels │ ├── Components │ │ └── Home │ │ │ ├── PostSeasonModel.cs │ │ │ ├── OpenModel.cs │ │ │ └── PreSeasonModel.cs │ ├── KeyValueModel.cs │ ├── ErrorViewModel.cs │ ├── SignInModel.cs │ └── SignUpModel.cs ├── CountryBaseModel.cs ├── ApiModels │ └── Order API │ │ ├── Request │ │ └── Order │ │ │ ├── Update │ │ │ ├── UpdateOrderRequestModel.cs │ │ │ ├── UpdateHiderModel.cs │ │ │ └── UpdateOrderModel.cs │ │ │ ├── Create │ │ │ ├── OrderModel.cs │ │ │ ├── UserModel.cs │ │ │ └── CreateOrderRequestModel.cs │ │ │ └── List │ │ │ └── ListRequestModel.cs │ │ └── Response │ │ └── OrderResponseModel.cs ├── BoilerPlate.Web.Entities.csproj └── SettingsModel.cs ├── BoilerPlate.Data.Entities ├── BoilerPlate.Data.Entities.csproj ├── BaseEntity.cs ├── CountryBaseEntity.cs ├── Country.cs ├── Cache.cs ├── ApplicationLogs.cs └── Setting.cs ├── BoilerPlate.Utils ├── Exceptions │ ├── APIErrorResponseWrapper.cs │ ├── ForbiddenException.cs │ ├── NotFoundException.cs │ ├── AuthenticationFailedException.cs │ ├── ApiErrorResponse.cs │ ├── ApiErrorResponseFactory.cs │ └── CustomException.cs ├── Configuration │ ├── CountryConfiguration.cs │ ├── Campaign.cs │ ├── ConnectionStrings.cs │ ├── Market.cs │ ├── ApplicationSettings.cs │ └── MarketConfiguration.cs ├── Enums │ ├── ApplicationUserRoles.cs │ ├── CampaignStatuses.cs │ └── Countries.cs ├── Authentication │ ├── ApplicationRole.cs │ └── ApplicationUser.cs ├── Helpers │ ├── ApiCaller │ │ ├── ContentType.cs │ │ ├── APICallResponseDTO.cs │ │ ├── APICallRequestDTO.cs │ │ └── RequestHelpers.cs │ ├── UrlHelper.cs │ ├── SerializationHelper.cs │ ├── SemaphoreLocker.cs │ ├── PredicateBuilder.cs │ ├── DateHelper.cs │ ├── SiteHelper.cs │ └── EnumHelper.cs ├── Constants │ ├── HangfireQueues.cs │ └── Constants.cs ├── Attributes │ └── IsTrueAttribute.cs ├── BoilerPlate.Utils.csproj └── Extensions │ ├── DistributedCaching.cs │ └── HttpRequestExtensions.cs ├── BoilerPlate.Service.Contract ├── IStatisticService.cs ├── IBulkOperationService.cs ├── IEmailService.cs ├── IEncryptionService.cs ├── ICacheService.cs ├── ICampaignService.cs ├── BoilerPlate.Service.Contract.csproj ├── ISettingService.cs └── IIdentityService.cs ├── BoilerPlate.Data.Contract ├── IDataContext.cs ├── BoilerPlate.Data.Contract.csproj ├── IHangfireDataContext.cs ├── IStatisticRepository.cs ├── ISettingRepository.cs └── IBaseRepository.cs ├── BoilerPlate.Test ├── Fakers │ ├── CreateOrderRequestModelFaker.cs │ ├── SignInModelFaker.cs │ └── OrderModelFaker.cs ├── TestBase.cs ├── TestConfig.cs ├── BoilerPlate.Test.csproj ├── PageObjects │ └── AdminSignInPageObject.cs ├── Drivers │ ├── WebDriver.cs │ └── WebDriverFactory.cs ├── BrowserTestBase.cs └── UserTests │ └── AdminTests.cs ├── .dockerignore ├── BoilerPlate.Bootstrapper ├── Authentication │ ├── EmailConfirmationTokenProviderOptions.cs │ ├── CustomEmailConfirmationTokenProvider.cs │ └── AppClaimsPrincipalFactory.cs ├── Authorization │ ├── HangfireAuthorizationFilter.cs │ ├── APIUserAuthorizeAttribute.cs │ └── CampaignSeasonAttribute.cs ├── ConfigurationExtension │ ├── HangfireConfigurationExtension.cs │ ├── SwaggerConfigurationExtension.cs │ └── EndpointConfigurationExtension.cs ├── RegisterSeedUsers.cs ├── LanguageRouteConstraint.cs ├── ServiceExtensions │ ├── BusinessServiceExtension.cs │ ├── ConfigurationServiceExtension.cs │ ├── LogExtension.cs │ ├── HangfireExtension.cs │ ├── MarketConfigurationExtension.cs │ ├── CultureExtension.cs │ └── RepositoryServiceExtension.cs ├── RouteDataRequestCultureProvider.cs ├── LocalizationConvention.cs ├── BoilerPlate.Bootstrapper.csproj ├── CultureAnchorTagHelper.cs ├── SecurityHeadersMiddleware.cs └── ResponseMiddleware.cs ├── BoilerPlate.Data ├── Repositories │ ├── StatisticRepository.cs │ ├── SettingRepository.cs │ └── BaseRepository.cs ├── BoilerPlate.Data.csproj └── DBContext │ ├── HangfireDataContext.cs │ ├── ApplicationDataContext.cs │ ├── BaseDataContext.cs │ ├── DbExtensions.cs │ └── ModelBuilderExtensions.cs ├── BoilerPlate.Service ├── StatisticService.cs ├── BulkOperationService.cs ├── BoilerPlate.Service.csproj ├── EmailService.cs ├── CacheService.cs ├── CampaignService.cs ├── EncryptionService.cs └── SettingService.cs └── README.md /BoilerPlate.Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /BoilerPlate.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alico/asp-net-6-mvc-boilerplate/HEAD/BoilerPlate.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BoilerPlate.Service.Entities/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BoilerPlate.Service.Entities 4 | { 5 | public class Class1 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ViewModels/Components/Home/PostSeasonModel.cs: -------------------------------------------------------------------------------- 1 | namespace BoilerPlate.Web.Entities 2 | { 3 | public record PostSeasonModel : CountryBaseModel 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /BoilerPlate.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 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Entities/BoilerPlate.Data.Entities.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BoilerPlate.Service.Entities/BoilerPlate.Service.Entities.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | @if (User.IsInRole("Admin")) 6 | { 7 | Layout = "~/Views/Shared/_AdminLayout.cshtml"; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/CountryBaseModel.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | 3 | namespace BoilerPlate.Web.Entities 4 | { 5 | public record CountryBaseModel 6 | { 7 | public Countries Country { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ViewModels/Components/Home/OpenModel.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace BoilerPlate.Web.Entities 3 | { 4 | public record OpenModel : CountryBaseModel 5 | { 6 | public string UserName { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/Components/Home/Open.cshtml: -------------------------------------------------------------------------------- 1 | @inject IIdentityService _identityService; 2 | @model BoilerPlate.Web.Entities.OpenModel 3 | 4 | @{ 5 | var country = SiteHelper.GetCountry(); 6 | } 7 |

The campaign is open

-------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /BoilerPlate.Web/appsettings.dev.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/Footer.cshtml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /BoilerPlate.Web/appsettings.production.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ViewModels/KeyValueModel.cs: -------------------------------------------------------------------------------- 1 | namespace BoilerPlate.Web.Entities 2 | { 3 | public record KeyValueModel 4 | { 5 | public string Key { get; set; } 6 | 7 | public string Value { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BoilerPlate.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 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Exceptions/APIErrorResponseWrapper.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace BoilerPlate.Utils 4 | { 5 | public class APIErrorResponseWrapper 6 | { 7 | [JsonProperty("error")] 8 | public object Error { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Exceptions/ForbiddenException.cs: -------------------------------------------------------------------------------- 1 | namespace BoilerPlate.Utils 2 | { 3 | public class ForbiddenException : CustomException 4 | { 5 | public ForbiddenException(string message = "Not Authorized.") : base(message) 6 | { 7 | 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /BoilerPlate.Utils/Exceptions/NotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace BoilerPlate.Utils 2 | { 3 | public class NotFoundException : CustomException 4 | { 5 | public NotFoundException(string message = "Couldn't find this item!") : base(message) 6 | { 7 | 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ViewModels/Components/Home/PreSeasonModel.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace BoilerPlate.Web.Entities 3 | { 4 | public record PreSeasonModel : CountryBaseModel 5 | { 6 | public SignUpModel SignUpModel { get; set; } 7 | public string OpeningDate { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /BoilerPlate.Data.Entities/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BoilerPlate.Data.Entities 5 | { 6 | [Serializable] 7 | public class BaseEntity 8 | { 9 | [Key] 10 | public virtual T Id { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BoilerPlate.Service.Contract/IStatisticService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace BoilerPlate.Service.Contract 7 | { 8 | public interface IStatisticService 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BoilerPlate.Service.Contract/IBulkOperationService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace BoilerPlate.Service.Contract 7 | { 8 | public interface IBulkOperationService 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ApiModels/Order API/Request/Order/Update/UpdateOrderRequestModel.cs: -------------------------------------------------------------------------------- 1 | namespace BoilerPlate.Web.Entities 2 | { 3 | public record UpdateOrderRequestModel 4 | { 5 | public UpdateOrderModel Order { get; set; } 6 | public UpdateUserModel User { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ViewModels/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BoilerPlate.Web.Entities 4 | { 5 | public record ErrorViewModel 6 | { 7 | public string RequestId { get; set; } 8 | 9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ApiModels/Order API/Request/Order/Update/UpdateHiderModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace BoilerPlate.Web.Entities 4 | { 5 | public record UpdateUserModel 6 | { 7 | [StringLength(50)] 8 | public string Token { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ApiModels/Order API/Request/Order/Update/UpdateOrderModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace BoilerPlate.Web.Entities 4 | { 5 | public record UpdateOrderModel 6 | { 7 | [StringLength(50)] 8 | public string Id { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Exceptions/AuthenticationFailedException.cs: -------------------------------------------------------------------------------- 1 | namespace BoilerPlate.Utils 2 | { 3 | public class AuthenticationFailedException : CustomException 4 | { 5 | public AuthenticationFailedException(string message = "Authentication failed!") : base(message) 6 | { 7 | 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/BoilerPlate.Web.Entities.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Configuration/CountryConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace BoilerPlate.Utils 2 | { 3 | public class CountryConfiguration 4 | { 5 | public string UK { get; set; } 6 | 7 | public string IE { get; set; } 8 | 9 | public string NZ { get; set; } 10 | 11 | public string Au { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Contract/IDataContext.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace BoilerPlate.Data.Contract 8 | { 9 | public interface IDataContext 10 | { 11 | void EnsureDbCreated(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Contract/BoilerPlate.Data.Contract.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /BoilerPlate.Service.Contract/IEmailService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace BoilerPlate.Service.Contract 6 | { 7 | public interface IEmailService 8 | { 9 | Task SendUserConfirmationEmail(Market market, ApplicationUser applicationUser, string confirmationLink); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Entities/CountryBaseEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BoilerPlate.Data.Entities 5 | { 6 | [Serializable] 7 | public class CountryBaseEntity : BaseEntity 8 | { 9 | public int CountryId { get; set; } 10 | public Country Country { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/Head.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @ViewData["Title"] - BoilerPlate.Web 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Contract/IHangfireDataContext.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace BoilerPlate.Data.Contract 8 | { 9 | public interface IHangfireDataContext 10 | { 11 | void EnsureDbCreated(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ApiModels/Order API/Request/Order/Create/OrderModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace BoilerPlate.Web.Entities 4 | { 5 | public record OrderModel 6 | { 7 | [StringLength(50)] 8 | [Required(ErrorMessage = "Order Id is required.")] 9 | public string Id { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ApiModels/Order API/Request/Order/Create/UserModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace BoilerPlate.Web.Entities 4 | { 5 | public record UserModel 6 | { 7 | [StringLength(50)] 8 | [Required(ErrorMessage = "User token is required.")] 9 | public string Token { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/GTM.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var gtmCode = SiteHelper.GetGtmCode(); 3 | } 4 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /BoilerPlate.Service.Contract/IEncryptionService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BoilerPlate.Service.Contract 4 | { 5 | public interface IEncryptionService 6 | { 7 | string Encrypt(int input); 8 | string Encrypt(string input); 9 | string Decrypt(string cipherText); 10 | T Decrypt(string cipherText) where T : struct; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Contract/IStatisticRepository.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace BoilerPlate.Data.Contract 8 | { 9 | public interface IStatisticRepository : IBaseRepository, Guid> 10 | { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Enums/ApplicationUserRoles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Identity; 3 | using System; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace BoilerPlate.Utils 7 | { 8 | public enum ApplicationUserRoles 9 | { 10 | None = 0, 11 | ApplicationUser = 1, 12 | Admin = 2, 13 | APIUser = 3 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BoilerPlate.Test/Fakers/CreateOrderRequestModelFaker.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using BoilerPlate.Web.Entities; 3 | 4 | namespace BoilerPlate.Test 5 | { 6 | public class CreateOrderRequestModelFaker : Faker 7 | { 8 | public CreateOrderRequestModelFaker() 9 | { 10 | RuleFor(x => x.Order, new OrderModelFaker().Generate()); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /BoilerPlate.Utils/Authentication/ApplicationRole.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using System; 3 | 4 | namespace BoilerPlate.Utils 5 | { 6 | public class ApplicationRole : IdentityRole 7 | { 8 | public ApplicationRole() 9 | { 10 | 11 | } 12 | 13 | public ApplicationRole(string name):base(name) 14 | { 15 | 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ViewModels/SignInModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace BoilerPlate.Web.Entities 4 | { 5 | public record SignInModel 6 | { 7 | [Required] 8 | public string UserName { get; set; } 9 | 10 | [Required] 11 | [DataType(DataType.Password)] 12 | public string Password { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Configuration/Campaign.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BoilerPlate.Utils 4 | { 5 | [Serializable] 6 | public class Campaign 7 | { 8 | public DateTime? SeasonStartDate { get; set; } 9 | public DateTime? SeasonEndDate { get; set; } 10 | public CampaignStatuses Status { get; set; } 11 | public Countries Country { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Configuration/ConnectionStrings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Identity; 3 | using System; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace BoilerPlate.Utils 7 | { 8 | public class ConnectionStrings 9 | { 10 | public string MainConnection { get; set; } 11 | public string HangfireConnection { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ApiModels/Order API/Response/OrderResponseModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BoilerPlate.Web.Entities 4 | { 5 | public record OrderResponseModel 6 | { 7 | public string Id { get; set; } 8 | public string OrderId { get; set; } 9 | public string UserId { get; set; } 10 | public DateTime CreationDate { get; set; } 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Enums/CampaignStatuses.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Identity; 3 | using System; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace BoilerPlate.Utils 7 | { 8 | [Serializable] 9 | public enum CampaignStatuses 10 | { 11 | None = 0, 12 | Open = 1, 13 | ClosePreSeason = 2, 14 | ClosedForSeason = 3 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BoilerPlate.Test/Fakers/SignInModelFaker.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using BoilerPlate.Web.Entities; 3 | 4 | namespace BoilerPlate.Test 5 | { 6 | public class SignInModelFaker : Faker 7 | { 8 | public SignInModelFaker() 9 | { 10 | RuleFor(x => x.UserName, x => x.Random.AlphaNumeric(10)); 11 | RuleFor(x => x.Password, x => x.Random.AlphaNumeric(10)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/ApiCaller/ContentType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace BoilerPlate.Utils 4 | { 5 | public enum ContentType 6 | { 7 | None = 0, 8 | [Description("application/json")] 9 | ApplicationJson = 1, 10 | 11 | [Description("text/plain")] 12 | Text = 2, 13 | 14 | [Description("multipart/form-data")] 15 | FormData = 3 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Configuration/Market.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BoilerPlate.Utils 4 | { 5 | public class Market 6 | { 7 | public Countries Country { get; set; } 8 | public string Host { get; set; } 9 | public Campaign Campaign { get; set; } 10 | public string CampaignStartDate { get { return DateHelper.GetLocalDateFormatted(Campaign.SeasonStartDate.Value, Country); } } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Entities/Country.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BoilerPlate.Data.Entities 5 | { 6 | public class Country 7 | { 8 | [Key] 9 | public int Id { get; set; } 10 | 11 | [StringLength(50)] 12 | public string Name { get; set; } 13 | public DateTime CreationDate { get; set; } 14 | public DateTime LastModifyDate { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Enums/Countries.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Identity; 3 | using System; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.ComponentModel; 6 | 7 | namespace BoilerPlate.Utils 8 | { 9 | [Serializable] 10 | public enum Countries 11 | { 12 | None = 0, 13 | [Description("en-GB")] 14 | UK = 1, 15 | [Description("en-Au")] 16 | Au = 2, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Constants/HangfireQueues.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Identity; 3 | using System; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace BoilerPlate.Utils 7 | { 8 | public class HangfireQueues 9 | { 10 | public const string Default = "default"; 11 | public const string CampaignStatus = "campaignstatus"; 12 | public const string ReminderEmails = "reminderemails"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BoilerPlate.Test/Fakers/OrderModelFaker.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using BoilerPlate.Web.Entities; 3 | 4 | namespace BoilerPlate.Test 5 | { 6 | public class OrderModelFaker : Faker 7 | { 8 | public OrderModelFaker() 9 | { 10 | RuleFor(x => x.Id, x => x.Random.AlphaNumeric(10)); 11 | //RuleFor(x => x.PostCode, x => x.Address.ZipCode("##? ?##")); 12 | //RuleFor(x => x.SKU, x => x.Commerce.Ean8()); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /BoilerPlate.Service.Contract/ICacheService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace BoilerPlate.Service.Contract 5 | { 6 | public interface ICacheService 7 | { 8 | Task Get(string cacheKey, TimeSpan memCacheExpireDate, Task func, bool purge = false) where T : class; 9 | Task Get(string cacheKey, TimeSpan distributedCacheExpireDate, TimeSpan memCacheExpireDate, Task func, bool purge = false) where T : class; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BoilerPlate.Service.Contract/ICampaignService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace BoilerPlate.Service.Contract 7 | { 8 | public interface ICampaignService 9 | { 10 | Task GetCampaign(Countries country, bool purgeCache = false); 11 | Task> GetCampaigns(bool purgeCache = false); 12 | Task GetCampaign(bool purgeCache = false); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ApiModels/Order API/Request/Order/Create/CreateOrderRequestModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace BoilerPlate.Web.Entities 4 | { 5 | public record CreateOrderRequestModel 6 | { 7 | [Required(ErrorMessage = "Order object must be set.")] 8 | public OrderModel Order { get; set; } 9 | 10 | 11 | [Required(ErrorMessage = "User object must be set.")] 12 | public UserModel User { get; set; } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/Authentication/EmailConfirmationTokenProviderOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace BoilerPlate.Bootstrapper 4 | { 5 | public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions 6 | { 7 | public EmailConfirmationTokenProviderOptions() 8 | { 9 | Name = "EmailDataProtectorTokenProvider"; 10 | TokenLifespan = Utils.Constants.EmailConfirmationTokenExpiration; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/ApiCaller/APICallResponseDTO.cs: -------------------------------------------------------------------------------- 1 | namespace BoilerPlate.Utils 2 | { 3 | public class APICallResponseDTO where T : class 4 | { 5 | public bool IsSucceded { get { return StatusCode == 200 || StatusCode == 201; } } 6 | 7 | public int StatusCode { get; set; } 8 | public T Body { get; set; } 9 | public string Error { get; set; } 10 | 11 | public APICallResponseDTO() 12 | { 13 | StatusCode = 200; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/UrlHelper.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Globalization; 3 | 4 | namespace BoilerPlate.Utils 5 | { 6 | public static class UrlHelper 7 | { 8 | public static Countries GetCountryByCode(string code) 9 | { 10 | return EnumHelper.GetValueFromDescription(code); 11 | } 12 | 13 | public static string GenerateAccountConfirmationLink(Market site) 14 | { 15 | return $"{site.Host}/confirm-email"; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Entities/Cache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BoilerPlate.Data.Entities 5 | { 6 | public class Cache 7 | { 8 | [StringLength(1000)] 9 | public string Id { get; set; } 10 | public byte[] Value { get; set; } 11 | public DateTimeOffset? ExpiresAtTime { get; set; } 12 | public long? SlidingExpirationInSeconds { get; set; } 13 | public DateTimeOffset? AbsoluteExpiration { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BoilerPlate.Service.Contract/BoilerPlate.Service.Contract.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Attributes/IsTrueAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BoilerPlate.Utils 5 | { 6 | public class IsTrueAttribute : ValidationAttribute 7 | { 8 | public override bool IsValid(object value) 9 | { 10 | if (value == null) return false; 11 | if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties."); 12 | 13 | return (bool)value; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ApiModels/Order API/Request/Order/List/ListRequestModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BoilerPlate.Web.Entities 5 | { 6 | public record ListRequestModel 7 | { 8 | [Range(1, 1000)] 9 | public int PageNumber { get; set; } 10 | 11 | [Range(1, 100)] 12 | public int PageItemCount { get; set; } 13 | 14 | public ListRequestModel() 15 | { 16 | PageNumber = 1; 17 | PageItemCount = 100; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Contract/ISettingRepository.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace BoilerPlate.Data.Contract 8 | { 9 | public interface ISettingRepository : IBaseRepository 10 | { 11 | Task GetByKey(short countryId, string key); 12 | Task> GetByGroupId(short countryId, int groupId); 13 | Task GetByValue(string value, short countryId); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | 2 | @using BoilerPlate.Web 3 | @using BoilerPlate.Web.Entities 4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 5 | @inject BoilerPlate.Utils.ApplicationSettings _applicationSettings; 6 | @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers 7 | @addTagHelper *, BoilerPlate.Web 8 | @addTagHelper *, BoilerPlate.Bootstrapper 9 | @addTagHelper *, BoilerPlate.TagHelpers 10 | @using Microsoft.AspNetCore.Mvc.Localization 11 | @using BoilerPlate.Service.Contract 12 | @using BoilerPlate.Utils 13 | 14 | -------------------------------------------------------------------------------- /BoilerPlate.Test/TestBase.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using NUnit; 3 | 4 | namespace BoilerPlate.Test 5 | { 6 | [TestFixtureSource(nameof(Sites))] 7 | [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] 8 | public abstract class TestBase 9 | { 10 | protected static readonly TestConfig[] Sites = 11 | { 12 | new TestConfig("UK", "localhost:44369"), 13 | }; 14 | 15 | protected TestConfig Config; 16 | 17 | protected TestBase(TestConfig config) 18 | { 19 | Config = (TestConfig)config.Clone(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /BoilerPlate.Web/appsettings.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "MainConnection": "", 4 | "HangfireConnection": "" 5 | }, 6 | "ApplicationSettings": { 7 | "Environment": "localhost", 8 | "Hosts": { 9 | "UK": "", 10 | "Au": "" 11 | } 12 | }, 13 | 14 | "Logging": { 15 | "LogLevel": { 16 | "Default": "Information", 17 | "Microsoft": "Warning", 18 | "Microsoft.Hosting.Lifetime": "Information" 19 | } 20 | }, 21 | 22 | "AllowedHosts": "*", 23 | 24 | "RecaptchaSettings": { 25 | "SecretKey": "", 26 | "SiteKey": "" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BoilerPlate.Web/appsettings.staging.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "MainConnection": "", 4 | "HangfireConnection": "" 5 | }, 6 | "ApplicationSettings": { 7 | "Environment": "localhost", 8 | "Hosts": { 9 | "UK": "", 10 | "Au": "" 11 | } 12 | }, 13 | 14 | "Logging": { 15 | "LogLevel": { 16 | "Default": "Information", 17 | "Microsoft": "Warning", 18 | "Microsoft.Hosting.Lifetime": "Information" 19 | } 20 | }, 21 | 22 | "AllowedHosts": "*", 23 | 24 | "RecaptchaSettings": { 25 | "SecretKey": "", 26 | "SiteKey": "" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/Authorization/HangfireAuthorizationFilter.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Hangfire.Dashboard; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | using System; 5 | using System.Globalization; 6 | 7 | namespace BoilerPlate.Bootstrapper 8 | { 9 | public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter 10 | { 11 | public bool Authorize(DashboardContext context) 12 | { 13 | var httpContext = context.GetHttpContext(); 14 | 15 | return httpContext.User.IsInRole(ApplicationUserRoles.Admin.ToString()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BoilerPlate.Web/appsettings.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "MainConnection": "", 4 | "HangfireConnection": "" 5 | }, 6 | "ApplicationSettings": { 7 | "Environment": "localhost", 8 | "Hosts": { 9 | "UK": "", 10 | "Au": "" 11 | } 12 | }, 13 | 14 | "Logging": { 15 | "LogLevel": { 16 | "Default": "Information", 17 | "Microsoft": "Warning", 18 | "Microsoft.Hosting.Lifetime": "Information" 19 | } 20 | }, 21 | 22 | "AllowedHosts": "*", 23 | 24 | "RecaptchaSettings": { 25 | "SecretKey": "", 26 | "SiteKey": "" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/SettingsModel.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace BoilerPlate.Web.Entities 6 | { 7 | public record SettingsModel 8 | { 9 | [Required(ErrorMessage = "StartDate must be set. ")] 10 | public DateTime StartDate { get; set; } 11 | 12 | [Required(ErrorMessage = "EndDate must be set. ")] 13 | public DateTime EndDate { get; set; } 14 | 15 | [StringLength(500)] 16 | public string APISecret { get; set; } 17 | 18 | public Countries Country { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/BoilerPlate.Utils.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ConfigurationExtension/HangfireConfigurationExtension.cs: -------------------------------------------------------------------------------- 1 | using Hangfire; 2 | using Microsoft.AspNetCore.Builder; 3 | 4 | namespace BoilerPlate.Bootstrapper 5 | { 6 | public static class HangfireConfigurationExtension 7 | { 8 | public static IApplicationBuilder ConfigureHangfire(this IApplicationBuilder app) 9 | { 10 | app.UseHangfireDashboard("/hangfire", new DashboardOptions() 11 | { 12 | DashboardTitle = "BoilerPlate - Hangfire", 13 | Authorization = new[] { new HangfireAuthorizationFilter() } 14 | }); 15 | return app; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BoilerPlate.Data/Repositories/StatisticRepository.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Utils; 4 | using Microsoft.EntityFrameworkCore; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Threading.Tasks; 10 | 11 | namespace BoilerPlate.Data 12 | { 13 | public class StatisticRepository : BaseRepository, Guid>, IStatisticRepository 14 | { 15 | public StatisticRepository(ConnectionStrings connectionStrings, IDataContext dataContext) : base(connectionStrings, dataContext) 16 | { 17 | 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/Components/Home/ClosePreSeason.cshtml: -------------------------------------------------------------------------------- 1 | @inject IViewLocalizer localizer; 2 | @inject IIdentityService _identityService; 3 | @model BoilerPlate.Web.Entities.PreSeasonModel; 4 | 5 | @{ 6 | var country = SiteHelper.GetCountry(); 7 | var title = SiteHelper.GetTitle(); 8 | } 9 | 10 |
11 | 12 | 13 |
14 |
15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
-------------------------------------------------------------------------------- /BoilerPlate.Data.Entities/ApplicationLogs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BoilerPlate.Data.Entities 5 | { 6 | public class ApplicationLogs : BaseEntity 7 | { 8 | [StringLength(50)] 9 | public Guid RequestId { get; set; } 10 | 11 | [StringLength(5000)] 12 | public string Message { get; set; } 13 | 14 | [StringLength(50)] 15 | public string Level { get; set; } 16 | 17 | public DateTime TimeStamp { get; set; } 18 | 19 | [StringLength(5000)] 20 | public string Exception { get; set; } 21 | 22 | [StringLength(5000)] 23 | public string Properties { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BoilerPlate.Service/StatisticService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Service.Contract; 4 | using BoilerPlate.Utils; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Security.Cryptography; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using System.Linq; 14 | using BoilerPlate.Web.Entities; 15 | 16 | namespace BoilerPlate.Service 17 | { 18 | public class StatisticService : IStatisticService 19 | { 20 | public StatisticService() 21 | { 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Entities/Setting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BoilerPlate.Data.Entities 5 | { 6 | [Serializable] 7 | public class Setting : CountryBaseEntity 8 | { 9 | public short? GroupId { get; set; } 10 | 11 | [StringLength(50)] 12 | public string Key { get; set; } 13 | 14 | [StringLength(1000)] 15 | public string Value { get; set; } 16 | 17 | public short Status { get; set; } 18 | 19 | [StringLength(250)] 20 | public string Description { get; set; } 21 | 22 | public DateTime CreationDate { get; set; } 23 | 24 | public DateTime LastModifyDate { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BoilerPlate.Service/BulkOperationService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Service.Contract; 4 | using BoilerPlate.Utils; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Security.Cryptography; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using System.Linq; 14 | using BoilerPlate.Web.Entities; 15 | 16 | namespace BoilerPlate.Service 17 | { 18 | public class BulkOperationService : IBulkOperationService 19 | { 20 | public BulkOperationService() 21 | { 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/ApiCaller/APICallRequestDTO.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BoilerPlate.Utils 4 | { 5 | public class APICallRequestDTO 6 | { 7 | public string Url { get; set; } 8 | public string MethodName { get; set; } 9 | public Dictionary RequestHeader { get; set; } 10 | public object RequestObject { get; set; } 11 | public ContentType ContentType { get; set; } 12 | public string Token { get; set; } 13 | 14 | public APICallRequestDTO() 15 | { 16 | ContentType = ContentType.ApplicationJson; 17 | Token = string.Empty; 18 | RequestHeader = new Dictionary(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BoilerPlate.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "MainConnection": "Server=localhost;Database=BoilerPlate;Trusted_Connection=True;", 4 | "HangfireConnection": "Server=localhost;Database=BoilerPlate.Hangfire;Trusted_Connection=True;" 5 | }, 6 | "AppSettings": { 7 | "Environment": "localhost", 8 | "Hosts": { 9 | "UK": "https://localhost:44369/", 10 | "Au": "https://localhost:44369/" 11 | } 12 | }, 13 | 14 | "Logging": { 15 | "LogLevel": { 16 | "Default": "Information", 17 | "Microsoft": "Warning", 18 | "Microsoft.Hosting.Lifetime": "Information" 19 | } 20 | }, 21 | 22 | "AllowedHosts": "*", 23 | 24 | "RecaptchaSettings": { 25 | "SecretKey": "", 26 | "SiteKey": "" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/RegisterSeedUsers.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service.Contract; 2 | using BoilerPlate.Utils; 3 | using Hangfire; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.ApplicationModels; 7 | using Microsoft.AspNetCore.Routing; 8 | using System.Linq; 9 | 10 | namespace BoilerPlate.Bootstrapper 11 | { 12 | public class RegisterSeedUsers 13 | { 14 | public static void Initialize() 15 | { 16 | var jobId = BackgroundJob.Enqueue(x => x.CreateRoles()); 17 | BackgroundJob.ContinueJobWith(jobId, x => x.CreateAdminUsers()); 18 | BackgroundJob.ContinueJobWith(jobId, x => x.CreateAPIUsers()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/Components/Home/ClosedForSeason.cshtml: -------------------------------------------------------------------------------- 1 | @inject IIdentityService _identityService; 2 | @inject IViewLocalizer localizer; 3 | 4 | @{ 5 | ViewData["Title"] = "Campaign is over"; 6 | } 7 | 8 | @{ 9 | var country = SiteHelper.GetCountry(); 10 | var title = SiteHelper.GetTitle(); 11 | } 12 | 13 |
14 |
15 |
16 |
17 |
18 |

19 | @localizer["CampaignIsOver"] 20 |

21 | 22 |
23 |
24 |
25 |
26 |
-------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ConfigurationExtension/SwaggerConfigurationExtension.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Builder; 3 | 4 | namespace BoilerPlate.Bootstrapper 5 | { 6 | public static class SwaggerConfigurationExtension 7 | { 8 | public static IApplicationBuilder ConfigureSwagger(this IApplicationBuilder app, 9 | ApplicationSettings applicationSettings) 10 | { 11 | 12 | if (!applicationSettings.IsProduction()) 13 | { 14 | app.UseSwagger(); 15 | app.UseSwaggerUI(c => 16 | { 17 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "BoilerPlate API V1"); 18 | }); 19 | } 20 | 21 | return app; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base 4 | WORKDIR /app 5 | EXPOSE 80 6 | EXPOSE 443 7 | 8 | FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build 9 | WORKDIR /src 10 | COPY ["BoilerPlate.Web/BoilerPlate.Web.csproj", "BoilerPlate.Web/"] 11 | RUN dotnet restore "BoilerPlate.Web/BoilerPlate.Web.csproj" 12 | COPY . . 13 | WORKDIR "/src/BoilerPlate.Web" 14 | RUN dotnet build "BoilerPlate.Web.csproj" -c Release -o /app/build 15 | 16 | FROM build AS publish 17 | RUN dotnet publish "BoilerPlate.Web.csproj" -c Release -o /app/publish 18 | 19 | FROM base AS final 20 | WORKDIR /app 21 | COPY --from=publish /app/publish . 22 | ENTRYPOINT ["dotnet", "BoilerPlate.Web.dll"] -------------------------------------------------------------------------------- /BoilerPlate.Service/BoilerPlate.Service.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Dockerfile.original: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base 4 | WORKDIR /app 5 | EXPOSE 80 6 | EXPOSE 443 7 | 8 | FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build 9 | WORKDIR /src 10 | COPY ["BoilerPlate.Web/BoilerPlate.Web.fsproj", "BoilerPlate.Web/"] 11 | RUN dotnet restore "BoilerPlate.Web/BoilerPlate.Web.fsproj" 12 | COPY . . 13 | WORKDIR "/src/BoilerPlate.Web" 14 | RUN dotnet build "BoilerPlate.Web.fsproj" -c Release -o /app/build 15 | 16 | FROM build AS publish 17 | RUN dotnet publish "BoilerPlate.Web.fsproj" -c Release -o /app/publish 18 | 19 | FROM base AS final 20 | WORKDIR /app 21 | COPY --from=publish /app/publish . 22 | ENTRYPOINT ["dotnet", "BoilerPlate.Web.dll"] -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/Authentication/CustomEmailConfirmationTokenProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace BoilerPlate.Bootstrapper 7 | { 8 | public class CustomEmailConfirmationTokenProvider 9 | : DataProtectorTokenProvider where TUser : class 10 | { 11 | public CustomEmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider, 12 | IOptions options, 13 | ILogger> logger) 14 | : base(dataProtectionProvider, options, logger) 15 | { 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Authentication/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Identity; 3 | using System; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace BoilerPlate.Utils 7 | { 8 | public class ApplicationUser : IdentityUser 9 | { 10 | public string FirstName { get; set; } 11 | public string Phone { get; set; } 12 | public int CountryId { get; set; } 13 | 14 | [NotMapped] 15 | public Countries Country { get; set; } 16 | 17 | public bool IsOptInMarketing { get; set; } 18 | public bool IsApprovedTermsConditions { get; set; } 19 | public bool IsOverThanAgeLimit { get; set; } 20 | public DateTime ExpireDate { get; set; } 21 | public DateTime CreationDate { get; set; } 22 | public DateTime LastmodifyDate { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Program-old.cs: -------------------------------------------------------------------------------- 1 | //using Microsoft.AspNetCore.Hosting; 2 | //using Microsoft.Extensions.Configuration; 3 | //using Microsoft.Extensions.Hosting; 4 | //using Microsoft.Extensions.Logging; 5 | //using Serilog; 6 | //using System; 7 | //using System.Collections.Generic; 8 | //using System.Linq; 9 | //using System.Threading.Tasks; 10 | 11 | //namespace BoilerPlate.Web 12 | //{ 13 | // public class Program 14 | // { 15 | // public static void Main(string[] args) 16 | // { 17 | // CreateHostBuilder(args).Build().Run(); 18 | // } 19 | 20 | // public static IHostBuilder CreateHostBuilder(string[] args) => 21 | // Host.CreateDefaultBuilder(args).UseSerilog() 22 | // .ConfigureWebHostDefaults(webBuilder => 23 | // { 24 | // webBuilder.UseStartup(); 25 | // }); 26 | // } 27 | //} 28 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Configuration/ApplicationSettings.cs: -------------------------------------------------------------------------------- 1 | namespace BoilerPlate.Utils 2 | { 3 | public class ApplicationSettings 4 | { 5 | public CountryConfiguration Hosts { get; set; } 6 | 7 | public string HostingEnvironmentPath { get; set; } 8 | 9 | public string Environment { get; set; } 10 | 11 | public bool IsProduction() 12 | { 13 | return Environment == "production"; 14 | } 15 | 16 | public bool IsStaging() 17 | { 18 | return Environment == "staging"; 19 | } 20 | 21 | public bool IsDevelopment() 22 | { 23 | return Environment == "development"; 24 | } 25 | 26 | public bool IsLocalhost() 27 | { 28 | return Environment == "localhost"; 29 | } 30 | 31 | public bool IsTest() 32 | { 33 | return Environment == "test"; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Exceptions/ApiErrorResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Collections.Generic; 3 | namespace BoilerPlate.Utils 4 | { 5 | public class ApiErrorResponse 6 | { 7 | [JsonProperty("correlationId")] 8 | public string CorrelationId { get; set; } 9 | 10 | [JsonIgnore] 11 | public int StatusCode { get; private set; } 12 | 13 | 14 | [JsonProperty("message")] 15 | public List Message { get; private set; } 16 | 17 | public ApiErrorResponse(int statusCode, string description) 18 | { 19 | this.StatusCode = statusCode; 20 | Message = new List(); 21 | this.Message.Add(description); 22 | } 23 | public ApiErrorResponse(int statusCode, List description) 24 | { 25 | this.StatusCode = statusCode; 26 | this.Message = description; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /BoilerPlate.Service.Contract/ISettingService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Entities; 2 | using BoilerPlate.Utils; 3 | using BoilerPlate.Web.Entities; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace BoilerPlate.Service.Contract 9 | { 10 | public interface ISettingService 11 | { 12 | Task GetByKey(Countries country, string key, bool purge = false); 13 | Task GetByKeyValue(string key, string value, bool purge = false); 14 | Task> GetByGroupId(Countries country, int groupId, bool purge = false); 15 | Task> GetAll(Countries country); 16 | Task Upsert(Countries country, string key, string value, short? groupId); 17 | Task Upsert(SettingsModel model); 18 | Task GetByValue(Countries country, string value, bool purge = false); 19 | 20 | Task ValueExists(Countries country, string value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BoilerPlate.Service/EmailService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service.Contract; 2 | using BoilerPlate.Utils; 3 | using Hangfire; 4 | using Microsoft.Extensions.Caching.Distributed; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using System; 7 | using System.IO; 8 | using System.Security.Cryptography; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace BoilerPlate.Service 13 | { 14 | public class EmailService : IEmailService 15 | { 16 | public readonly IMemoryCache _cache; 17 | public readonly IDistributedCache _distributedCache; 18 | 19 | public EmailService(IMemoryCache cache, IDistributedCache distributedCache) 20 | { 21 | _distributedCache = distributedCache; 22 | _cache = cache; 23 | } 24 | 25 | public async Task SendUserConfirmationEmail(Market market, ApplicationUser applicationUser, string confirmationLink) 26 | { 27 | //BackgroundJob.Enqueue(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model BoilerPlate.Web.Entities.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 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |

26 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/LanguageRouteConstraint.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Routing; 4 | using System.Linq; 5 | 6 | namespace BoilerPlate.Bootstrapper 7 | { 8 | public class LanguageRouteConstraint : IRouteConstraint 9 | { 10 | public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) 11 | { 12 | if (!values.ContainsKey("culture")) 13 | return false; 14 | 15 | var cultureSlugs = EnumHelper.GetEnumList().Select(x => new { Key = x, Value = x.GetDescription() }).ToDictionary(x => x.Key, x => x.Value); 16 | var culture = values["culture"].ToString(); 17 | 18 | return cultureSlugs.Any(x => x.Key.ToString().Contains(culture, System.StringComparison.OrdinalIgnoreCase) 19 | || x.Value.ToString().Contains(culture, System.StringComparison.OrdinalIgnoreCase)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BoilerPlate.Data/BoilerPlate.Data.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ServiceExtensions/BusinessServiceExtension.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service; 2 | using BoilerPlate.Service.Contract; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace BoilerPlate.Bootstrapper 6 | { 7 | public static class BusinessServiceExtension 8 | { 9 | public static IServiceCollection AddBusinessServices(this IServiceCollection services) 10 | { 11 | services.AddTransient(); 12 | services.AddTransient(); 13 | services.AddTransient(); 14 | services.AddTransient(); 15 | services.AddTransient(); 16 | services.AddTransient(); 17 | services.AddTransient(); 18 | services.AddTransient(); 19 | 20 | 21 | return services; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/Authorization/APIUserAuthorizeAttribute.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | using System; 4 | using System.Globalization; 5 | 6 | namespace BoilerPlate.Bootstrapper 7 | { 8 | 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 10 | public class APIUserAuthorizeAttribute : Attribute, IAuthorizationFilter 11 | { 12 | public void OnAuthorization(AuthorizationFilterContext context) 13 | { 14 | var user = (ApplicationUser)context.HttpContext.Items["User"]; 15 | if (user is not null) 16 | { 17 | //Override Current culture with user's 18 | if (user.Country is not Countries.None) 19 | { 20 | CultureInfo.CurrentCulture = new CultureInfo(user.Country.GetDescription(), false); 21 | CultureInfo.CurrentUICulture = new CultureInfo(user.Country.GetDescription(), false); 22 | } 23 | } 24 | else 25 | throw new AuthenticationFailedException(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BoilerPlate.Data.Contract/IBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace BoilerPlate.Data.Contract 8 | { 9 | public interface IBaseRepository where T : BaseEntity 10 | { 11 | Task GetByIdAsync(TE id); 12 | Task FirstOrDefaultAsync(Expression> predicate); 13 | Task AnyAsync(Expression> predicate); 14 | Task AddAsync(T entity); 15 | Task AddRangeAsync(IEnumerable entities); 16 | Task UpdateAsync(T entity); 17 | Task UpdateRangeAsync(IEnumerable entities); 18 | Task RemoveAsync(T entity); 19 | Task CommitAsync(); 20 | Task> GetAllAsync(); 21 | Task> GetWhereAsync(Expression> predicate); 22 | Task> GetAsync(Expression> predicate, int count, int pageNumber); 23 | Task CountAllAsync(); 24 | Task CountWhereAsync(Expression> predicate); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Exceptions/ApiErrorResponseFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BoilerPlate.Utils 4 | { 5 | public class ApiErrorResponseFactory 6 | { 7 | public static ApiErrorResponse Create(Exception ex) 8 | { 9 | if (ex.GetType() == typeof(NotFoundException)) 10 | return new ApiErrorResponse(404, ex.Message); 11 | 12 | else if (ex.GetType() == typeof(ForbiddenException)) 13 | return new ApiErrorResponse(403, ex.Message); 14 | 15 | else if (ex.GetType() == typeof(AuthenticationFailedException)) 16 | return new ApiErrorResponse(401, ex.Message); 17 | 18 | else if (ex.GetType() == typeof(CustomException)) 19 | { 20 | if (((CustomException)ex).Messages != null) 21 | return new ApiErrorResponse(400, ((CustomException)ex).Messages); 22 | else 23 | return new ApiErrorResponse(400, ex.Message); 24 | } 25 | else 26 | return new ApiErrorResponse(400, "An error occurred during processing the request."); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/SerializationHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.Serialization.Formatters.Binary; 3 | 4 | namespace BoilerPlate.Utils 5 | { 6 | public static class Serialization 7 | { 8 | public static byte[] ToByteArray(this object obj) 9 | { 10 | if (obj == null) 11 | { 12 | return null; 13 | } 14 | BinaryFormatter binaryFormatter = new BinaryFormatter(); 15 | using (MemoryStream memoryStream = new MemoryStream()) 16 | { 17 | binaryFormatter.Serialize(memoryStream, obj); 18 | return memoryStream.ToArray(); 19 | } 20 | } 21 | public static T FromByteArray(this byte[] byteArray) where T : class 22 | { 23 | if (byteArray == null) 24 | { 25 | return default(T); 26 | } 27 | BinaryFormatter binaryFormatter = new BinaryFormatter(); 28 | using (MemoryStream memoryStream = new MemoryStream(byteArray)) 29 | { 30 | return binaryFormatter.Deserialize(memoryStream) as T; 31 | } 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using BoilerPlate.Web.Entities; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace BoilerPlate.Web.Controllers 13 | { 14 | public class HomeController : Controller 15 | { 16 | private readonly ILogger _logger; 17 | public HomeController(ILogger logger, RoleManager roleManager) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public IActionResult Index() 23 | { 24 | return View(); 25 | } 26 | 27 | public IActionResult Privacy() 28 | { 29 | return View(); 30 | } 31 | 32 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 33 | public IActionResult Error() 34 | { 35 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ServiceExtensions/ConfigurationServiceExtension.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace BoilerPlate.Bootstrapper 7 | { 8 | public static class ConfigurationServiceExtension 9 | { 10 | public static IServiceCollection AddConfigurationServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment hostEnvironment 11 | , out ApplicationSettings applicationSettings 12 | , out ConnectionStrings connectionStrings) 13 | { 14 | connectionStrings = new ConnectionStrings(); 15 | applicationSettings = new ApplicationSettings(); 16 | 17 | configuration.GetSection("ConnectionStrings").Bind(connectionStrings); 18 | configuration.GetSection("AppSettings").Bind(applicationSettings); 19 | applicationSettings.HostingEnvironmentPath = hostEnvironment.ContentRootPath; 20 | 21 | services.AddSingleton(connectionStrings); 22 | services.AddSingleton(applicationSettings); 23 | 24 | return services; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Extensions/DistributedCaching.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Distributed; 2 | using Microsoft.Extensions.Caching.Memory; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace BoilerPlate.Utils 8 | { 9 | public static class DistributedCaching 10 | { 11 | public static async Task SetAsync(this IDistributedCache distributedCache, string key, T value, DateTime absoluteExpiration, TimeSpan? slidingExpiration = null, CancellationToken token = default(CancellationToken)) 12 | { 13 | var cacheOptions = new DistributedCacheEntryOptions 14 | { 15 | AbsoluteExpiration = absoluteExpiration, 16 | SlidingExpiration = slidingExpiration 17 | }; 18 | 19 | await distributedCache.SetAsync(key, value.ToByteArray(), cacheOptions, token); 20 | } 21 | 22 | public static async Task GetAsync(this IDistributedCache distributedCache, string key, CancellationToken token = default(CancellationToken)) where T : class 23 | { 24 | var result = await distributedCache.GetAsync(key, token); 25 | return result.FromByteArray(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BoilerPlate.Data/DBContext/HangfireDataContext.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Utils; 4 | using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | using System; 10 | 11 | namespace BoilerPlate.Data 12 | { 13 | public class HangfireDataContext : DbContext, IHangfireDataContext 14 | { 15 | private readonly ConnectionStrings _connectionStrings; 16 | 17 | public HangfireDataContext() { } 18 | 19 | public HangfireDataContext(ConnectionStrings connectionStrings) => (_connectionStrings) = (connectionStrings); 20 | 21 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 22 | { 23 | optionsBuilder.UseSqlServer(_connectionStrings.HangfireConnection); 24 | } 25 | 26 | protected override void OnModelCreating(ModelBuilder modelBuilder) 27 | { 28 | base.OnModelCreating(modelBuilder); 29 | } 30 | 31 | public void EnsureDbCreated() 32 | { 33 | this.Database.EnsureCreated(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/SemaphoreLocker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace BoilerPlate.Utils 6 | { 7 | public class SemaphoreLocker 8 | { 9 | private readonly SemaphoreSlim _semaphore; 10 | 11 | public SemaphoreLocker() 12 | { 13 | _semaphore = new SemaphoreSlim(1, 1); 14 | } 15 | 16 | public async Task LockAsync(Func worker) 17 | { 18 | await _semaphore.WaitAsync(); 19 | try 20 | { 21 | await worker(); 22 | } 23 | finally 24 | { 25 | _semaphore.Release(); 26 | } 27 | } 28 | 29 | // overloading variant for non-void methods with return type (generic T) 30 | public async Task LockAsync(Func> worker) 31 | { 32 | await _semaphore.WaitAsync(); 33 | 34 | T result = default; 35 | 36 | try 37 | { 38 | result = await worker(); 39 | } 40 | finally 41 | { 42 | _semaphore.Release(); 43 | } 44 | 45 | return result; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BoilerPlate.Web.Entities/ViewModels/SignUpModel.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace BoilerPlate.Web.Entities 6 | { 7 | public record SignUpModel : CountryBaseModel 8 | { 9 | 10 | [StringLength(20)] 11 | [Required(ErrorMessage = "FirstName is required.")] 12 | public string FirstName { get; set; } 13 | 14 | [StringLength(50)] 15 | [EmailAddress] 16 | [Required(ErrorMessage = "Email is Required.")] 17 | public string Email { get; set; } 18 | 19 | [StringLength(10)] 20 | [DataType(DataType.Password)] 21 | [Required(ErrorMessage = "Password is Required.")] 22 | public string Password { get; set; } 23 | 24 | [IsTrue] 25 | [Required(ErrorMessage = "Terms&Conditions must be checked.")] 26 | public bool TermsConditions { get; set; } 27 | 28 | [IsTrue] 29 | [Required(ErrorMessage = "You must be 16 years or older to use this service.")] 30 | public bool AgeCheck { get; set; } 31 | 32 | public bool MarketingOptIn { get; set; } 33 | 34 | public string TermsConditionsHTML { get; set; } 35 | 36 | public string ReturnUrl { get; set; } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ConfigurationExtension/EndpointConfigurationExtension.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Builder; 3 | 4 | namespace BoilerPlate.Bootstrapper 5 | { 6 | public static class EndpointConfigurationExtension 7 | { 8 | public static IApplicationBuilder ConfigureEndpoints(this IApplicationBuilder app, ApplicationSettings applicationSettings) 9 | { 10 | if (applicationSettings.IsDevelopment() || applicationSettings.IsTest()) 11 | { 12 | app.UseEndpoints(endpoints => 13 | { 14 | endpoints.MapControllerRoute( 15 | name: "default", 16 | pattern: "{culture:culture}/{controller=Home}/{action=Index}/{id?}", new 17 | { 18 | culture = "en" 19 | }); 20 | }); 21 | } 22 | else 23 | { 24 | app.UseEndpoints(endpoints => 25 | { 26 | endpoints.MapControllerRoute( 27 | name: "default", 28 | pattern: "{controller=Home}/{action=Index}/{id?}"); 29 | }); 30 | } 31 | return app; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/PredicateBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | 5 | namespace BoilerPlate.Utils 6 | { 7 | public static class PredicateBuilder 8 | { 9 | public static Expression> True() { return f => true; } 10 | public static Expression> False() { return f => false; } 11 | 12 | public static Expression> Or(this Expression> expr1, 13 | Expression> expr2) 14 | { 15 | var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast()); 16 | return Expression.Lambda> 17 | (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters); 18 | } 19 | 20 | public static Expression> And(this Expression> expr1, 21 | Expression> expr2) 22 | { 23 | var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast()); 24 | return Expression.Lambda> 25 | (Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BoilerPlate.Test/TestConfig.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | using System; 3 | 4 | namespace BoilerPlate.Test 5 | { 6 | public class TestConfig : ICloneable 7 | { 8 | public IWebDriver Driver { get; set; } 9 | public string SiteName { get; set; } 10 | public string Domain { get; set; } 11 | public string RootUrl => $"https://{Domain}/"; 12 | public string RootUrlInsecure => $"http://{Domain}/"; 13 | public string BrowserName { get; set; } 14 | 15 | public TestConfig(string siteName, string domain) 16 | { 17 | SiteName = siteName; 18 | Domain = domain; 19 | } 20 | public TestConfig(TestConfig domainConfig, string browserName) 21 | { 22 | Driver = domainConfig.Driver; 23 | SiteName = domainConfig.SiteName; 24 | Domain = domainConfig.Domain; 25 | BrowserName = browserName; 26 | } 27 | 28 | public override string ToString() 29 | { 30 | if (string.IsNullOrWhiteSpace(BrowserName)) 31 | { 32 | return SiteName; 33 | } 34 | return $"{SiteName} - {BrowserName}"; 35 | } 36 | 37 | public object Clone() 38 | { 39 | return new TestConfig(this, BrowserName); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /BoilerPlate.Test/BoilerPlate.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/DateHelper.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using System; 3 | using System.Globalization; 4 | 5 | namespace BoilerPlate.Utils 6 | { 7 | public static class DateHelper 8 | { 9 | public static string GetLocalDateFormatted(DateTime date, Countries country) 10 | { 11 | TimeZoneInfo timeZone = null; 12 | var pattern = "ddd dd MMM hh:mm ttt"; 13 | var formattedDate = string.Empty; 14 | 15 | switch (country) 16 | { 17 | case Countries.Au: 18 | { 19 | timeZone = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time"); 20 | var offset = new DateTimeOffset(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, TimeSpan.Zero); 21 | var localDate = TimeZoneInfo.ConvertTimeFromUtc(offset.UtcDateTime, timeZone); 22 | formattedDate = localDate.ToString(pattern); 23 | break; 24 | } 25 | 26 | default: 27 | { 28 | formattedDate = date.ToString(pattern); 29 | break; 30 | } 31 | } 32 | 33 | return formattedDate; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BoilerPlate.Web/BoilerPlate.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | false 6 | d8d98037-9e8d-4cc6-9b62-f90317ff410e 7 | Linux 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Exceptions/CustomException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | 6 | namespace BoilerPlate.Utils 7 | { 8 | public class CustomException : Exception 9 | { 10 | public List Messages { get; set; } 11 | 12 | public CustomException(string message) : base(message) 13 | { 14 | 15 | } 16 | 17 | public CustomException(Exception exception) : base(exception.Message, exception) 18 | { 19 | 20 | } 21 | 22 | public CustomException(string message, Exception exception) : base(message, exception) 23 | { 24 | 25 | } 26 | 27 | public CustomException(IEnumerable message) : base(string.Join(",", message.Distinct())) 28 | { 29 | Messages = message.ToList(); 30 | } 31 | 32 | public CustomException(IEnumerable message, Exception exception) : base(string.Join("#", message.Distinct()), exception) 33 | { 34 | 35 | } 36 | 37 | public override string ToString() 38 | { 39 | if (InnerException == null) 40 | { 41 | return base.ToString(); 42 | } 43 | 44 | return string.Format(CultureInfo.InvariantCulture, "{0} [See nested exception: {1}]", base.ToString(), InnerException); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BoilerPlate.Data/Repositories/SettingRepository.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Utils; 4 | using Microsoft.EntityFrameworkCore; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Threading.Tasks; 10 | 11 | namespace BoilerPlate.Data 12 | { 13 | public class SettingRepository : BaseRepository, ISettingRepository 14 | { 15 | public SettingRepository(ConnectionStrings connectionStrings, IDataContext dataContext) : base(connectionStrings, dataContext) 16 | { 17 | 18 | } 19 | 20 | public async Task> GetByGroupId(short countryId, int groupId) 21 | { 22 | return await _dataContext.Set().Where(x => x.GroupId == groupId && x.CountryId == countryId).ToListAsync(); 23 | } 24 | 25 | public async Task GetByKey(short countryId, string key) 26 | { 27 | return await _dataContext.Set().Where(x => x.Key == key && x.CountryId == countryId).FirstOrDefaultAsyncNoLock(); 28 | } 29 | 30 | public async Task GetByValue(string value, short countryId) 31 | { 32 | return await _dataContext.Set().Where(x => x.Value == value && x.CountryId == countryId).FirstOrDefaultAsyncNoLock(); 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Configuration/MarketConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace BoilerPlate.Utils 6 | { 7 | public class MarketConfiguration 8 | { 9 | public Dictionary SupportedCultures { get; set; } 10 | public string DefaultCulture { get; set; } 11 | public string DefaultSlug { get; set; } 12 | public List Countries { get; set; } 13 | public Dictionary Hosts { get; set; } 14 | public Dictionary CultureSlugs { get; set; } 15 | 16 | public string GetCultureByPath(string culture) 17 | { 18 | var country = SupportedCultures.Where(x => x.Value.Contains(culture, System.StringComparison.OrdinalIgnoreCase)).Select(x => x.Key).FirstOrDefault(); 19 | if (country == default(Countries)) 20 | country = Utils.Countries.UK; 21 | 22 | return country.GetDescription(); ; 23 | } 24 | 25 | public string GetCultureByHost(string host) 26 | { 27 | var country = Hosts.Where(x => x.Value.Contains(host, System.StringComparison.OrdinalIgnoreCase)).Select(x => x.Key).FirstOrDefault(); 28 | if (country == default(Countries)) 29 | country = Utils.Countries.UK; 30 | 31 | return country.GetDescription(); ; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BoilerPlate.Test/PageObjects/AdminSignInPageObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Interactions; 4 | using SeleniumExtras.PageObjects; 5 | using SeleniumExtras.WaitHelpers; 6 | 7 | namespace BoilerPlate.Test 8 | { 9 | public class AdminSignInPageObject 10 | { 11 | private IWebDriver _driver; 12 | private TestConfig _config; 13 | 14 | public AdminSignInPageObject(IWebDriver webDriver, TestConfig config) 15 | { 16 | _driver = webDriver; 17 | _config = config; 18 | } 19 | 20 | private IWebElement _userNameTextField => _driver.FindElement(By.Name("UserName")); 21 | private IWebElement _passwordTextField => _driver.FindElement(By.Name("Password")); 22 | private IWebElement _submitButton => _driver.FindElement(By.ClassName("btn-primary")); 23 | 24 | public void SetSingInPageFields(string userName, string password) 25 | { 26 | _userNameTextField.Clear(); 27 | _userNameTextField.SendKeys(userName); 28 | 29 | _passwordTextField.Clear(); 30 | _passwordTextField.SendKeys(password); 31 | } 32 | 33 | public void SubmitButton_Click() 34 | { 35 | _submitButton.Click(); 36 | } 37 | 38 | public void GoToAdminPage() 39 | { 40 | _driver.Navigate().GoToUrl($"{_config.RootUrl}admin"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/Authentication/AppClaimsPrincipalFactory.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.Extensions.Options; 4 | using System; 5 | using System.Security.Claims; 6 | using System.Threading.Tasks; 7 | 8 | namespace BoilerPlate.Bootstrapper 9 | { 10 | public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory 11 | { 12 | public AppClaimsPrincipalFactory( 13 | UserManager userManager 14 | , RoleManager roleManager 15 | , IOptions optionsAccessor) 16 | : base(userManager, roleManager, optionsAccessor) 17 | { } 18 | 19 | public async override Task CreateAsync(ApplicationUser user) 20 | { 21 | var principal = await base.CreateAsync(user); 22 | 23 | if (!string.IsNullOrWhiteSpace(user.FirstName)) 24 | { 25 | ((ClaimsIdentity)principal.Identity).AddClaims(new[] { 26 | new Claim(ClaimTypes.GivenName, user.FirstName) 27 | }); 28 | } 29 | 30 | if (!string.IsNullOrWhiteSpace(user.FirstName)) 31 | { 32 | ((ClaimsIdentity)principal.Identity).AddClaims(new[] { 33 | new Claim(ClaimTypes.Name, user.FirstName), 34 | }); 35 | } 36 | 37 | return principal; 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /BoilerPlate.Data/DBContext/ApplicationDataContext.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Utils; 4 | using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | using System; 10 | 11 | namespace BoilerPlate.Data 12 | { 13 | public class ApplicationDataContext : BaseDataContext, IDataContext 14 | { 15 | public ApplicationDataContext():base() { } 16 | 17 | public ApplicationDataContext(ConnectionStrings connectionStrings):base(connectionStrings) 18 | { 19 | } 20 | 21 | protected override void OnModelCreating(ModelBuilder modelBuilder) 22 | { 23 | base.OnModelCreating(modelBuilder); 24 | 25 | //PrimaryKeys 26 | 27 | modelBuilder.Entity().HasKey(x => x.Id); 28 | modelBuilder.Entity().HasKey(x => x.Id); 29 | modelBuilder.Entity().HasKey(x => x.Id); 30 | 31 | //Relations 32 | modelBuilder.Seed(); 33 | } 34 | 35 | public void EnsureDbCreated() 36 | { 37 | this.Database.EnsureCreated(); 38 | } 39 | 40 | public DbSet ApplicationLogs { get; set; } 41 | public DbSet Settings { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BoilerPlate.Test/Drivers/WebDriver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Support.UI; 4 | 5 | namespace BoilerPlate.Test 6 | { 7 | public class WebDriver : IDisposable 8 | { 9 | public WebDriverFactory WebDriverFactory { get; set; } 10 | private readonly Lazy _currentWebDriverLazy; 11 | private readonly Lazy _waitLazy; 12 | private readonly TimeSpan _waitDuration = TimeSpan.FromSeconds(10); 13 | private bool _isDisposed; 14 | 15 | public WebDriver() 16 | { 17 | _currentWebDriverLazy = new Lazy(GetWebDriver); 18 | _waitLazy = new Lazy(GetWebDriverWait); 19 | } 20 | 21 | public IWebDriver Current => _currentWebDriverLazy.Value; 22 | public WebDriverWait Wait => _waitLazy.Value; 23 | 24 | private WebDriverWait GetWebDriverWait() 25 | { 26 | return new WebDriverWait(Current, _waitDuration); 27 | } 28 | 29 | private IWebDriver GetWebDriver() 30 | { 31 | var testBrowserName = "chrome"; 32 | return WebDriverFactory.Create(testBrowserName); 33 | } 34 | 35 | public void Dispose() 36 | { 37 | if (_isDisposed) 38 | { 39 | return; 40 | } 41 | 42 | if (_currentWebDriverLazy.IsValueCreated) 43 | { 44 | Current.Quit(); 45 | } 46 | 47 | _isDisposed = true; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Extensions/HttpRequestExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Net.Http.Headers; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace BoilerPlate.Utils 7 | { 8 | public static class HttpRequestExtensions 9 | { 10 | private const string RequestedWithHeader = "X-Requested-With"; 11 | private const string XmlHttpRequest = "XMLHttpRequest"; 12 | const string jsonMime = "application/json"; 13 | 14 | public static bool IsAjaxRequest(this HttpRequest request) 15 | { 16 | if (request == null) 17 | throw new ArgumentNullException("request"); 18 | 19 | bool rtn = false; 20 | if (request.Headers != null) 21 | { 22 | bool isFetch = false; 23 | bool isAjax = request.Headers[RequestedWithHeader] == XmlHttpRequest; 24 | string acceptTypes = request.Headers[HeaderNames.Accept]; 25 | 26 | if (!string.IsNullOrEmpty(acceptTypes)) 27 | { 28 | rtn = acceptTypes.ToLower().Contains(jsonMime, StringComparison.OrdinalIgnoreCase); 29 | if (request.ContentType != null) 30 | isFetch = rtn || request.ContentType.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Any(t => t.Equals(jsonMime, StringComparison.OrdinalIgnoreCase)); 31 | } 32 | 33 | return isAjax || isFetch; 34 | } 35 | 36 | 37 | 38 | return false; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/SiteHelper.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Globalization; 3 | 4 | namespace BoilerPlate.Utils 5 | { 6 | public static class SiteHelper 7 | { 8 | public static string GetHost(ApplicationSettings applicationSettings, Countries country) 9 | { 10 | return country switch 11 | { 12 | Countries.UK => applicationSettings.Hosts.UK, 13 | Countries.Au => applicationSettings.Hosts.Au, 14 | _ => throw new NotFoundException("Host couldn't be found"), 15 | }; 16 | } 17 | 18 | 19 | public static Countries GetCountry() 20 | { 21 | var country = Countries.None; 22 | var currentCulture = CultureInfo.CurrentUICulture; 23 | if (currentCulture is not null) 24 | country = EnumHelper.GetValueFromDescription(currentCulture.Name); 25 | 26 | if (country is Countries.None) 27 | country = Countries.UK; 28 | 29 | return country; 30 | } 31 | 32 | public static string GetGtmCode() 33 | { 34 | var country = GetCountry(); 35 | return country switch 36 | { 37 | Countries.UK => "", 38 | Countries.Au => "", 39 | _ => "", 40 | }; 41 | } 42 | 43 | public static string GetTitle() 44 | { 45 | return "BoilerPlate"; 46 | } 47 | 48 | public static string GetDescription() 49 | { 50 | return "Lorem ipsum"; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Account/SignIn.cshtml: -------------------------------------------------------------------------------- 1 | @using reCAPTCHA.AspNetCore 2 | @using reCAPTCHA.AspNetCore.Versions; 3 | @using Microsoft.Extensions.Options 4 | @inject IOptions RecaptchaSettings; 5 | 6 | @model BoilerPlate.Web.Entities.SignInModel 7 | 8 | @{ 9 | ViewData["Title"] = "SignIn"; 10 | Layout = "~/Views/Shared/_Layout.cshtml"; 11 | } 12 | 13 |

SignIn

14 | 15 |
16 |
17 |
18 |
19 | @Html.AntiForgeryToken() 20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 |
31 |
32 | @(Html.Recaptcha(RecaptchaSettings.Value)) 33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 | 41 | 42 | @section Scripts { 43 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} 44 | } 45 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Admin/SignIn.cshtml: -------------------------------------------------------------------------------- 1 | @model BoilerPlate.Web.Entities.SignInModel 2 | 3 | @using reCAPTCHA.AspNetCore 4 | @using reCAPTCHA.AspNetCore.Versions; 5 | @using Microsoft.Extensions.Options 6 | @inject IOptions RecaptchaSettings; 7 | 8 | @{ 9 | ViewData["Title"] = "SignIn"; 10 | Layout = "~/Views/Shared/_Layout.cshtml"; 11 | } 12 | 13 |

SignIn

14 | 15 |

AdminSignInModel

16 |
17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 | @(Html.Recaptcha(RecaptchaSettings.Value)) 34 |
35 | 36 |
37 | 38 |
39 |
40 |
41 |
42 | 43 | 44 | @section Scripts { 45 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} 46 | } 47 | -------------------------------------------------------------------------------- /BoilerPlate.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 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | /* Provide sufficient contrast against white background */ 11 | a { 12 | color: #0366d6; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 22 | color: #fff; 23 | background-color: #1b6ec2; 24 | border-color: #1861ac; 25 | } 26 | 27 | /* Sticky footer styles 28 | -------------------------------------------------- */ 29 | html { 30 | font-size: 14px; 31 | } 32 | @media (min-width: 768px) { 33 | html { 34 | font-size: 16px; 35 | } 36 | } 37 | 38 | .border-top { 39 | border-top: 1px solid #e5e5e5; 40 | } 41 | .border-bottom { 42 | border-bottom: 1px solid #e5e5e5; 43 | } 44 | 45 | .box-shadow { 46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 47 | } 48 | 49 | button.accept-policy { 50 | font-size: 1rem; 51 | line-height: inherit; 52 | } 53 | 54 | /* Sticky footer styles 55 | -------------------------------------------------- */ 56 | html { 57 | position: relative; 58 | min-height: 100%; 59 | } 60 | 61 | body { 62 | /* Margin bottom by footer height */ 63 | margin-bottom: 60px; 64 | } 65 | .footer { 66 | position: absolute; 67 | bottom: 0; 68 | width: 100%; 69 | white-space: nowrap; 70 | line-height: 60px; /* Vertically center the text there */ 71 | } 72 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/Authorization/CampaignSeasonAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Filters; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Routing; 7 | using BoilerPlate.Utils; 8 | using System.Linq; 9 | using BoilerPlate.Service.Contract; 10 | 11 | namespace BoilerPlate.Bootstrapper 12 | { 13 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 14 | public class CampaignSeasonAttribute : Attribute, IActionFilter 15 | { 16 | public CampaignStatuses[] CampaignStatuses { get; set; } 17 | 18 | public CampaignSeasonAttribute(params CampaignStatuses[] campaignStatuses) 19 | { 20 | CampaignStatuses = campaignStatuses; 21 | } 22 | 23 | public void OnActionExecuted(ActionExecutedContext context) 24 | { 25 | 26 | } 27 | 28 | public void OnActionExecuting(ActionExecutingContext filterContext) 29 | { 30 | if (CampaignStatuses is not null) 31 | { 32 | var campaignService = filterContext.HttpContext.RequestServices.GetService(); 33 | var campaign = campaignService.GetCampaign().Result; 34 | 35 | if (!CampaignStatuses.Any(x => x == campaign.Status)) 36 | { 37 | filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new 38 | { 39 | controller = "Home", 40 | action = "Index" 41 | })); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Constants/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Identity; 3 | using System; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace BoilerPlate.Utils 7 | { 8 | public static class Constants 9 | { 10 | public static readonly short CampaignSettings = 1; 11 | public static readonly string SeasonStartDate = "SeasonStartDate"; 12 | public static readonly string SeasonEndDateKey = "SeasonEndDate"; 13 | public static readonly string CampaignCacheKey = "Campaign"; 14 | public static readonly string ISODateFormat = "yyyy-MM-dd hh:mm"; 15 | public static readonly string ClientSecret = "ClientSecret"; 16 | public static readonly string AdminUserPassword = "qwert123"; 17 | public static readonly string APIUserPassword = "qwert123"; 18 | 19 | public const string Admin = "Admin"; 20 | 21 | public static readonly string SymetricKey = "287f2d39-2c26-41e3-9896-ca6fb220d426"; 22 | public static readonly string DataProtectorToken = "3a1298f3-574e-4b48-b274-986d9cc918b8"; 23 | public static readonly string EncryptionSymetrickey = "GTY@!$QPUTXMZLH"; 24 | public static readonly byte[] EncryptionIv = new byte[] { 0x30, 0x26, 0x45, 0x5e, 0x10, 0x6d, 0x35, 0x62, 0x26, 0x65, 0x64, 0x15, 0x36 }; 25 | public static readonly string Statistics = "Statistics"; 26 | 27 | public static readonly TimeSpan EmailConfirmationTokenExpiration = TimeSpan.FromDays(180); 28 | public static readonly TimeSpan CookieExpiration = TimeSpan.FromDays(180); 29 | public static readonly TimeSpan UserSessionExpiration = TimeSpan.FromDays(180); 30 | public static short HangfireWorkerCount = 1; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/RouteDataRequestCultureProvider.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service.Contract; 2 | using BoilerPlate.Utils; 3 | using Hangfire; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Localization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.ApplicationModels; 8 | using Microsoft.AspNetCore.Routing; 9 | using System; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | using System.Web; 13 | 14 | namespace BoilerPlate.Bootstrapper 15 | { 16 | public class RouteDataRequestCultureProvider : RequestCultureProvider 17 | { 18 | public int IndexOfCulture; 19 | public ApplicationSettings ApplicationSettings; 20 | public MarketConfiguration MarketConfiguration; 21 | 22 | 23 | public override Task DetermineProviderCultureResult(HttpContext httpContext) 24 | { 25 | if (httpContext == null) 26 | throw new ArgumentNullException(nameof(httpContext)); 27 | 28 | var culture = string.Empty; 29 | var twoLetterCultureName = string.Empty; 30 | if (httpContext.Request.QueryString.HasValue) 31 | { 32 | var queryString = HttpUtility.ParseQueryString(httpContext.Request.QueryString.Value); 33 | twoLetterCultureName = queryString["culture"]; 34 | } 35 | 36 | if (string.IsNullOrEmpty(twoLetterCultureName)) 37 | twoLetterCultureName = httpContext.Request.Path.Value.Split('/')[IndexOfCulture]?.ToString(); 38 | 39 | culture = MarketConfiguration.GetCultureByPath(twoLetterCultureName); 40 | 41 | var providerResultCulture = new ProviderCultureResult(culture, culture); 42 | return Task.FromResult(providerResultCulture); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ServiceExtensions/LogExtension.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Serilog; 4 | using Serilog.Events; 5 | using Serilog.Sinks.MSSqlServer; 6 | using System; 7 | using System.Collections.ObjectModel; 8 | using System.Data; 9 | 10 | namespace BoilerPlate.Bootstrapper 11 | { 12 | public static class LogExtension 13 | { 14 | public static IServiceCollection AddLogging(this IServiceCollection services, ConnectionStrings connectionStrings) 15 | { 16 | try 17 | { 18 | var sinkOptions = new MSSqlServerSinkOptions(); 19 | sinkOptions.AutoCreateSqlTable = false; 20 | sinkOptions.TableName = "ApplicationLogs"; 21 | 22 | var columnOption = new ColumnOptions(); 23 | columnOption.Store.Remove(StandardColumn.MessageTemplate); 24 | columnOption.Level.NonClusteredIndex = true; 25 | columnOption.AdditionalColumns = new Collection 26 | { 27 | new SqlColumn 28 | {ColumnName = "RequestId", PropertyName = "RequestId", DataType = SqlDbType.NVarChar, DataLength = 64}, 29 | }; 30 | 31 | Log.Logger = new LoggerConfiguration() 32 | .Enrich.WithCorrelationId() 33 | .MinimumLevel.Override("Microsoft", LogEventLevel.Error) 34 | .MinimumLevel.Error() 35 | .WriteTo.MSSqlServer(connectionStrings.MainConnection, sinkOptions, columnOptions: columnOption) 36 | .CreateLogger(); 37 | } 38 | catch (Exception ex) 39 | { 40 | } 41 | return services; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/LocalizationConvention.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.ApplicationModels; 5 | using Microsoft.AspNetCore.Routing; 6 | using System.Linq; 7 | 8 | namespace BoilerPlate.Bootstrapper 9 | { 10 | public class LocalizationConvention : IApplicationModelConvention 11 | { 12 | public void Apply(ApplicationModel application) 13 | { 14 | var culturePrefix = new AttributeRouteModel(new RouteAttribute("{culture}")); 15 | 16 | foreach (var controller in application.Controllers) 17 | { 18 | foreach (var action in controller.Actions) 19 | { 20 | var attributes = action.Attributes.OfType().ToArray(); 21 | foreach (var attribute in attributes) 22 | { 23 | SelectorModel defaultSelector = action.Selectors.First(); 24 | 25 | var oldAttributeRouteModel = defaultSelector.AttributeRouteModel; 26 | var newAttributeRouteModel = new AttributeRouteModel(new RouteAttribute("{culture}")); 27 | 28 | newAttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(culturePrefix, defaultSelector.AttributeRouteModel); 29 | 30 | if (!action.Selectors.Any(s => s.AttributeRouteModel.Template == newAttributeRouteModel.Template)) 31 | { 32 | action.Selectors.Insert(0, new SelectorModel(defaultSelector) 33 | { 34 | AttributeRouteModel = newAttributeRouteModel 35 | }); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/EnumHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace BoilerPlate.Utils 8 | { 9 | public static class EnumHelper 10 | { 11 | public static string GetDescription(this Enum value) 12 | { 13 | return 14 | value 15 | .GetType() 16 | .GetMember(value.ToString()) 17 | .FirstOrDefault() 18 | ?.GetCustomAttribute() 19 | ?.Description 20 | ?? value.ToString(); 21 | } 22 | 23 | 24 | public static T GetValueFromDescription(string description) 25 | { 26 | var type = typeof(T); 27 | if (!type.IsEnum) throw new InvalidOperationException(); 28 | foreach (var field in type.GetFields()) 29 | { 30 | var attribute = Attribute.GetCustomAttribute(field, 31 | typeof(DescriptionAttribute)) as DescriptionAttribute; 32 | if (attribute != null) 33 | { 34 | if (attribute.Description.Contains(description, StringComparison.InvariantCultureIgnoreCase)) 35 | return (T)field.GetValue(null); 36 | } 37 | else 38 | { 39 | if (field.Name.Contains(description, StringComparison.InvariantCultureIgnoreCase)) 40 | return (T)field.GetValue(null); 41 | } 42 | } 43 | return default(T); 44 | } 45 | 46 | public static List GetEnumList() 47 | { 48 | var enums = ((T[])Enum.GetValues(typeof(T))).Where(x => !x.Equals(default(T))); 49 | return enums.ToList(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BoilerPlate.Service.Contract/IIdentityService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using BoilerPlate.Web.Entities; 3 | using Microsoft.AspNetCore.Identity; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Security.Claims; 7 | using System.Threading.Tasks; 8 | 9 | namespace BoilerPlate.Service.Contract 10 | { 11 | public interface IIdentityService 12 | { 13 | #region Customer Identity Services 14 | Task Register(ApplicationUser user); 15 | 16 | Task Update(ApplicationUser user); 17 | 18 | Task UpdateConsumerId(string id, string consumerId); 19 | Task SignIn(ApplicationUser user); 20 | Task SignIn(string userName, string password, bool rememberMe); 21 | bool IsSignedIn(ClaimsPrincipal principal); 22 | Task Logout(); 23 | Task Login(string email, string password, bool rememberMe); 24 | 25 | Task GenerateEmailConfirmationTokenAsync(string email); 26 | Task GenerateEmailConfirmationTokenAsync(ApplicationUser user); 27 | 28 | Task GenerateSignInLink(Market site, ApplicationUser applicationUser, string returnUrl = null); 29 | bool Any(string email); 30 | Task FindByEmailAsync(string email); 31 | Task FindActiveUserByEmailAsync(string email); 32 | Task FindByUserId(string id); 33 | Task ConfirmEmailAsync(ApplicationUser user, string token); 34 | Task AddToRoleAsync(ApplicationUser user, string role); 35 | Task CreateUserAccountIfNotExits(Market market, string email, string firstName); 36 | #endregion 37 | 38 | #region Seed Users 39 | Task CreateRoles(); 40 | Task CreateAdminUsers(); 41 | Task CreateAPIUsers(); 42 | #endregion 43 | 44 | #region Customer Identity Services 45 | Task AuthenticateAPIUser(string token); 46 | #endregion 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:61283/", 7 | "sslPort": 44369 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "development" 16 | } 17 | }, 18 | "Custom": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:12926;http://localhost:35334" 25 | }, 26 | "IIS (Staging)": { 27 | "commandName": "IISExpress", 28 | "launchBrowser": true, 29 | "environmentVariables": { 30 | "ASPNETCORE_ENVIRONMENT": "Staging" 31 | } 32 | }, 33 | "IIS Express (Dev)": { 34 | "commandName": "IISExpress", 35 | "launchBrowser": true, 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "dev" 38 | } 39 | }, 40 | "IIS Express (Test)": { 41 | "commandName": "IISExpress", 42 | "launchBrowser": true, 43 | "environmentVariables": { 44 | "ASPNETCORE_ENVIRONMENT": "test" 45 | } 46 | }, 47 | "IIS (Production)": { 48 | "commandName": "IISExpress", 49 | "launchBrowser": true, 50 | "environmentVariables": { 51 | "ASPNETCORE_ENVIRONMENT": "Production" 52 | } 53 | }, 54 | "BoilerPlate.Web": { 55 | "commandName": "Project", 56 | "launchBrowser": true, 57 | "environmentVariables": { 58 | "ENVIRONMENT": "development" 59 | }, 60 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 61 | }, 62 | "Docker": { 63 | "commandName": "Docker", 64 | "launchBrowser": true, 65 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", 66 | "publishAllPorts": true, 67 | "useSSL": true 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/BoilerPlate.Bootstrapper.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 34 |
35 |
36 |
37 | @RenderBody() 38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | @await RenderSectionAsync("Scripts", required: false) 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ServiceExtensions/HangfireExtension.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Utils; 3 | using Hangfire; 4 | using Hangfire.SqlServer; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using System; 7 | 8 | namespace BoilerPlate.Bootstrapper 9 | { 10 | public static class HangfireExtension 11 | { 12 | public static IServiceCollection AddHangfire(this IServiceCollection services, ConnectionStrings connectionStrings) 13 | { 14 | var hangfireDbContext = services.BuildServiceProvider().GetService(); 15 | hangfireDbContext.EnsureDbCreated(); 16 | 17 | services.AddHangfire(configuration => configuration 18 | .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) 19 | .UseSimpleAssemblyNameTypeSerializer() 20 | .UseRecommendedSerializerSettings() 21 | .UseSqlServerStorage(connectionStrings.HangfireConnection, new SqlServerStorageOptions 22 | { 23 | CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), 24 | SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), 25 | QueuePollInterval = TimeSpan.FromSeconds(15), 26 | UseRecommendedIsolationLevel = true, 27 | UsePageLocksOnDequeue = true, 28 | DisableGlobalLocks = true, 29 | })); 30 | 31 | services.AddHangfireServer(options => 32 | { 33 | options.ServerName = String.Format("{0}.{1}", Environment.MachineName, Guid.NewGuid().ToString()); 34 | options.WorkerCount = Constants.HangfireWorkerCount; 35 | options.Queues = new[] { 36 | HangfireQueues.Default, 37 | }; 38 | }); 39 | 40 | services.AddHangfireServer(options => 41 | { 42 | options.ServerName = String.Format("{0}.{1}", Environment.MachineName, HangfireQueues.ReminderEmails.ToString()); 43 | options.WorkerCount = 1; 44 | options.Queues = new[] { 45 | HangfireQueues.ReminderEmails, 46 | }; 47 | }); 48 | 49 | return services; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Controllers/AdminController.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service.Contract; 2 | using BoilerPlate.Utils; 3 | using BoilerPlate.Web.Entities; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Caching.Distributed; 7 | using Microsoft.Extensions.Logging; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Diagnostics; 11 | using System.Linq; 12 | using System.Threading.Tasks; 13 | 14 | namespace BoilerPlate.Web.Controllers 15 | { 16 | public class AdminController : BaseController 17 | { 18 | private readonly IIdentityService _identityService; 19 | 20 | public AdminController(IIdentityService identityService, 21 | UserManager userManager, 22 | ICampaignService campaignService, 23 | ILogger logger, 24 | ApplicationSettings applicationSettings, 25 | IDistributedCache cache, 26 | IEncryptionService encryptionService) : base (userManager, campaignService, logger, applicationSettings, cache, encryptionService) 27 | { 28 | _identityService = identityService; 29 | } 30 | 31 | 32 | [Route("admin")] 33 | public IActionResult SignIn() 34 | { 35 | return View(); 36 | } 37 | 38 | [HttpPost] 39 | [Route("admin")] 40 | [AutoValidateAntiforgeryToken] 41 | public async Task SignIn(SignInModel model) 42 | { 43 | if (ModelState.IsValid) 44 | { 45 | var signInResult = await _identityService.SignIn(model.UserName, model.Password, false); 46 | if (signInResult.Succeeded) 47 | { 48 | var user = await _userManager.FindByNameAsync(model.UserName); 49 | var roles = await _userManager.GetRolesAsync(user); 50 | 51 | if (roles.Contains(ApplicationUserRoles.Admin.ToString())) 52 | return RedirectToAction("Create", "Settings", new { countryCode = (short)Countries.UK }); 53 | else 54 | await _identityService.Logout(); 55 | } 56 | } 57 | 58 | ModelState.AddModelError("", "Invalid Login Attempt"); 59 | return View(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ServiceExtensions/MarketConfigurationExtension.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace BoilerPlate.Bootstrapper 10 | { 11 | public static class MarketConfigurationExtension 12 | { 13 | public static IServiceCollection AddMarkets(this IServiceCollection services, ApplicationSettings applicationSettings, out MarketConfiguration marketConfiguration) 14 | { 15 | var countries = EnumHelper.GetEnumList(); 16 | marketConfiguration = new MarketConfiguration() 17 | { 18 | Countries = countries, 19 | CultureSlugs = countries.Select(x => new { Key = x, Value = x.GetDescription()?.Split("-").LastOrDefault() ?? "" }).ToDictionary(x => x.Key, x => x.Value), 20 | DefaultCulture = Countries.UK.GetDescription(), 21 | DefaultSlug = Countries.UK.GetDescription()?.Split("-").LastOrDefault() ?? "", 22 | SupportedCultures = countries.Select(x => new { Key = x, Value = x.GetDescription() }).ToDictionary(x => x.Key, x => x.Value), 23 | Hosts = GetHosts(applicationSettings) 24 | }; 25 | 26 | services.AddSingleton(marketConfiguration); 27 | return services; 28 | } 29 | 30 | private static Dictionary GetHosts(ApplicationSettings applicationSettings) 31 | { 32 | Dictionary hosts = new Dictionary(); 33 | 34 | PropertyInfo[] properties = typeof(ApplicationSettings).GetProperties(); 35 | foreach (PropertyInfo property in applicationSettings.Hosts.GetType().GetProperties()) 36 | { 37 | Countries country = Countries.None; 38 | Enum.TryParse(property.Name, out country); 39 | 40 | if (country != Countries.None) 41 | { 42 | var value = property.GetValue(applicationSettings.Hosts)?.ToString() ?? string.Empty; 43 | if (!string.IsNullOrEmpty(value)) 44 | { 45 | hosts.Add(country, value); 46 | } 47 | } 48 | } 49 | return hosts; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/CultureAnchorTagHelper.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc.TagHelpers; 4 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 5 | using Microsoft.AspNetCore.Razor.TagHelpers; 6 | 7 | namespace BoilerPlate.Bootstrapper 8 | { 9 | 10 | [HtmlTargetElement("a", Attributes = ActionAttributeName)] 11 | [HtmlTargetElement("a", Attributes = ControllerAttributeName)] 12 | [HtmlTargetElement("a", Attributes = AreaAttributeName)] 13 | [HtmlTargetElement("a", Attributes = PageAttributeName)] 14 | [HtmlTargetElement("a", Attributes = PageHandlerAttributeName)] 15 | [HtmlTargetElement("a", Attributes = FragmentAttributeName)] 16 | [HtmlTargetElement("a", Attributes = HostAttributeName)] 17 | [HtmlTargetElement("a", Attributes = ProtocolAttributeName)] 18 | [HtmlTargetElement("a", Attributes = RouteAttributeName)] 19 | [HtmlTargetElement("a", Attributes = RouteValuesDictionaryName)] 20 | [HtmlTargetElement("a", Attributes = RouteValuesPrefix + "*")] 21 | public class CultureAnchorTagHelper : AnchorTagHelper 22 | { 23 | private const string ActionAttributeName = "asp-action"; 24 | private const string ControllerAttributeName = "asp-controller"; 25 | private const string AreaAttributeName = "asp-area"; 26 | private const string PageAttributeName = "asp-page"; 27 | private const string PageHandlerAttributeName = "asp-page-handler"; 28 | private const string FragmentAttributeName = "asp-fragment"; 29 | private const string HostAttributeName = "asp-host"; 30 | private const string ProtocolAttributeName = "asp-protocol"; 31 | private const string RouteAttributeName = "asp-route"; 32 | private const string RouteValuesDictionaryName = "asp-all-route-data"; 33 | private const string RouteValuesPrefix = "asp-route-"; 34 | 35 | 36 | public CultureAnchorTagHelper(IHtmlGenerator generator, IHttpContextAccessor contextAccessor, ApplicationSettings applicationSettings, MarketConfiguration marketConfiguration) : base(generator) 37 | { 38 | if (applicationSettings.IsDevelopment() || applicationSettings.IsTest()) 39 | { 40 | var culture = contextAccessor.HttpContext.Request.RouteValues["culture"]?.ToString() ?? marketConfiguration.DefaultSlug; 41 | RouteValues["culture"] = culture; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BoilerPlate.Service/CacheService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service.Contract; 2 | using BoilerPlate.Utils; 3 | using Microsoft.Extensions.Caching.Distributed; 4 | using Microsoft.Extensions.Caching.Memory; 5 | using System; 6 | using System.IO; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace BoilerPlate.Service 12 | { 13 | public class CacheService : ICacheService 14 | { 15 | public readonly IMemoryCache _cache; 16 | public readonly IDistributedCache _distributedCache; 17 | 18 | public CacheService(IMemoryCache cache, IDistributedCache distributedCache) 19 | { 20 | _distributedCache = distributedCache; 21 | _cache = cache; 22 | } 23 | 24 | public async Task Get(string cacheKey, TimeSpan memCacheExpireDate, Task func, bool purge = false) where T : class 25 | { 26 | var value = _cache.Get(cacheKey); 27 | 28 | if (value is null || purge) 29 | { 30 | value = await func; 31 | using (var entry = _cache.CreateEntry(cacheKey)) 32 | { 33 | entry.Value = value; 34 | entry.AbsoluteExpirationRelativeToNow = memCacheExpireDate; 35 | entry.SlidingExpiration = null; 36 | } 37 | } 38 | 39 | return value; 40 | } 41 | 42 | public async Task Get(string cacheKey, TimeSpan distributedCacheExpireDate, TimeSpan memCacheExpireDate, Task func, bool purge = false) where T : class 43 | { 44 | var value = _cache.Get(cacheKey); 45 | var expireDate = DateTime.Now.Add(distributedCacheExpireDate); 46 | 47 | if (value is null || purge) 48 | { 49 | value = await _distributedCache.GetAsync(cacheKey); 50 | if (value is null || purge) 51 | { 52 | value = await func; 53 | await _distributedCache.SetAsync(cacheKey, value, expireDate); 54 | } 55 | 56 | using (var entry = _cache.CreateEntry(cacheKey)) 57 | { 58 | entry.Value = value; 59 | entry.AbsoluteExpirationRelativeToNow = memCacheExpireDate; 60 | entry.SlidingExpiration = null; 61 | } 62 | } 63 | 64 | return value; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /BoilerPlate.Data/DBContext/BaseDataContext.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Utils; 4 | using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | using System; 10 | 11 | namespace BoilerPlate.Data 12 | { 13 | public abstract class BaseDataContext : IdentityDbContext, IDataProtectionKeyContext 14 | where TUser : IdentityUser 15 | where TRole : IdentityRole 16 | where TKey : IEquatable 17 | { 18 | private readonly ConnectionStrings _connectionStrings; 19 | 20 | public BaseDataContext() { } 21 | 22 | public BaseDataContext(ConnectionStrings connectionStrings) => (_connectionStrings) = (connectionStrings); 23 | 24 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 25 | { 26 | optionsBuilder.UseSqlServer(_connectionStrings.MainConnection); 27 | } 28 | 29 | protected override void OnModelCreating(ModelBuilder modelBuilder) 30 | { 31 | base.OnModelCreating(modelBuilder); 32 | //Relations 33 | 34 | #region ASP.NET Core Identity Tables 35 | modelBuilder.Entity().ToTable("Users").HasKey(x => x.Id); 36 | 37 | modelBuilder.Entity(entity => 38 | { 39 | entity.ToTable(name: "Role"); 40 | }); 41 | modelBuilder.Entity>(entity => 42 | { 43 | entity.ToTable("UserRoles"); 44 | }); 45 | 46 | modelBuilder.Entity>(entity => 47 | { 48 | entity.ToTable("UserClaims"); 49 | }); 50 | 51 | modelBuilder.Entity>(entity => 52 | { 53 | entity.ToTable("UserLogins"); 54 | entity.HasIndex(x => x.ProviderKey); 55 | }); 56 | 57 | modelBuilder.Entity>(entity => 58 | { 59 | entity.ToTable("RoleClaims"); 60 | 61 | }); 62 | 63 | modelBuilder.Entity>(entity => 64 | { 65 | entity.ToTable("UserTokens"); 66 | }); 67 | #endregion 68 | } 69 | public DbSet DataProtectionKeys { get; set; } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Account/SignUp.cshtml: -------------------------------------------------------------------------------- 1 | @using reCAPTCHA.AspNetCore 2 | @using reCAPTCHA.AspNetCore.Versions; 3 | @using Microsoft.Extensions.Options 4 | @inject IOptions RecaptchaSettings; 5 | 6 | 7 | @model BoilerPlate.Web.Entities.SignUpModel 8 | 9 | @{ 10 | ViewData["Title"] = "SignUp"; 11 | Layout = "~/Views/Shared/_Layout.cshtml"; 12 | } 13 | 14 |

SignUp

15 | 16 |
17 |
18 |
19 |
20 | @Html.AntiForgeryToken() 21 |
22 | 23 |
24 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 |
33 |
34 | 35 | 36 | 37 |
38 |
39 | 42 |
43 |
44 | 47 |
48 | 49 |
50 | 53 |
54 | 55 |
56 | @(Html.Recaptcha(RecaptchaSettings.Value)) 57 |
58 | 59 |
60 | 61 |
62 |
63 |
64 |
65 | 66 | 67 | @section Scripts { 68 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} 69 | } 70 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Controllers/ViewComponents/HomeViewComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | using Newtonsoft.Json; 8 | using System.Web; 9 | using BoilerPlate.Service.Contract; 10 | using BoilerPlate.Utils; 11 | using BoilerPlate.Web.Entities; 12 | 13 | namespace BoilerPlate.Web.Controllers 14 | { 15 | public class HomeViewComponent : ViewComponent 16 | { 17 | private readonly ICampaignService _campaignService; 18 | private readonly UserManager _userManager; 19 | public HomeViewComponent(ICampaignService campaignService, UserManager userManager) 20 | { 21 | _campaignService = campaignService; 22 | _userManager = userManager; 23 | } 24 | public async Task InvokeAsync() 25 | { 26 | var country = GetCountry(); 27 | var campaign = await _campaignService.GetCampaign(country); 28 | switch (campaign.Status) 29 | { 30 | case CampaignStatuses.ClosePreSeason: 31 | { 32 | var model = new PreSeasonModel() 33 | { 34 | Country = country, 35 | OpeningDate = new DateTimeOffset(campaign.SeasonStartDate.Value).ToUnixTimeMilliseconds().ToString() 36 | }; 37 | 38 | return View("ClosePreSeason", model); 39 | } 40 | case CampaignStatuses.ClosedForSeason: 41 | return View("ClosedForSeason"); 42 | default: 43 | { 44 | var model = new OpenModel() 45 | { 46 | Country = country, 47 | 48 | }; 49 | if (User?.Identity?.IsAuthenticated == true) 50 | { 51 | var user = await _userManager.GetUserAsync(HttpContext.User); 52 | model.UserName = user.FirstName; 53 | } 54 | 55 | return View("Open", model); 56 | } 57 | } 58 | } 59 | 60 | private Countries GetCountry() 61 | { 62 | Countries country = Countries.None; 63 | var currentCulture = CultureInfo.CurrentUICulture; 64 | if (currentCulture is not null) 65 | country = EnumHelper.GetValueFromDescription(currentCulture.Name); 66 | 67 | if (country == Countries.None) 68 | country = Countries.UK; 69 | 70 | return country; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Settings/Create.cshtml: -------------------------------------------------------------------------------- 1 | @model BoilerPlate.Web.Entities.SettingsModel 2 | 3 | @{ 4 | ViewData["Title"] = "Settings"; 5 | Layout = "~/Views/Shared/_AdminLayout.cshtml"; 6 | } 7 | 8 |
9 |
10 | 11 |

Settings

12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 31 | 32 |
33 |
34 | 35 | 36 | 37 |
38 |
39 | 40 | 41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | 49 |
50 |
51 | 52 |
53 |
54 |
55 |
56 | 57 |
58 |
59 | 60 | @section scripts{ 61 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Views/Shared/_AdminLayout.cshtml: -------------------------------------------------------------------------------- 1 | @inject IViewLocalizer localizer; 2 | @inject IIdentityService _identityService; 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | @if (User.IsInRole("Admin")) 15 | { 16 | 50 | } 51 |
52 |
53 |
54 | @RenderBody() 55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | @await RenderSectionAsync("Scripts", required: false) 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ServiceExtensions/CultureExtension.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Localization; 4 | using Microsoft.AspNetCore.Localization.Routing; 5 | using Microsoft.AspNetCore.Mvc.Razor; 6 | using Microsoft.AspNetCore.Routing; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using System.Data; 9 | using System.Globalization; 10 | using System.Linq; 11 | using System.Text.Json; 12 | 13 | namespace BoilerPlate.Bootstrapper 14 | { 15 | public static class CultureExtension 16 | { 17 | public static IServiceCollection AddCulture(this IServiceCollection services, ApplicationSettings applicationSettings, MarketConfiguration marketConfiguration) 18 | { 19 | services.AddLocalization(options => options.ResourcesPath = "Resources"); 20 | 21 | //dev or test env INSERT culture constraint into the route' 22 | if (applicationSettings.IsDevelopment() || applicationSettings.IsTest()) 23 | { 24 | services.Configure(options => 25 | { 26 | options.ConstraintMap.Add("culture", typeof(LanguageRouteConstraint)); 27 | }); 28 | services.AddMvc(opts => 29 | { 30 | opts.Conventions.Insert(0, new LocalizationConvention()); 31 | }).AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix) 32 | .AddDataAnnotationsLocalization() 33 | .AddJsonOptions(options => 34 | { 35 | options.JsonSerializerOptions.IgnoreNullValues = true; 36 | options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; 37 | }); 38 | } 39 | else 40 | { 41 | services.AddMvc().AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix) 42 | .AddDataAnnotationsLocalization() 43 | .AddJsonOptions(options => 44 | { 45 | options.JsonSerializerOptions.IgnoreNullValues = true; 46 | options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; 47 | }); 48 | } 49 | 50 | var supportedCultures = marketConfiguration.SupportedCultures.Values.Select(x => new CultureInfo(x)).ToList(); 51 | services.Configure( 52 | options => 53 | { 54 | options.DefaultRequestCulture = new RequestCulture(marketConfiguration.DefaultCulture); 55 | options.SupportedCultures = supportedCultures; 56 | options.SupportedUICultures = supportedCultures; 57 | 58 | options.RequestCultureProviders = new[]{ new RouteDataRequestCultureProvider{ 59 | IndexOfCulture=1, 60 | ApplicationSettings = applicationSettings, 61 | MarketConfiguration = marketConfiguration 62 | }}; 63 | }); 64 | 65 | return services; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Controllers/SettingsController.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service.Contract; 2 | using BoilerPlate.Utils; 3 | using BoilerPlate.Web.Entities; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Caching.Distributed; 8 | using Microsoft.Extensions.Logging; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Diagnostics; 12 | using System.Linq; 13 | using System.Threading.Tasks; 14 | 15 | namespace BoilerPlate.Web.Controllers 16 | { 17 | public class SettingsController : BaseController 18 | { 19 | private readonly ISettingService _settingService; 20 | public SettingsController(UserManager userManager, 21 | ApplicationSettings applicationSettings, 22 | ISettingService settingService, 23 | ICampaignService campaignService, 24 | IDistributedCache cache, 25 | IEncryptionService encryptionService, 26 | ILogger logger) : base(userManager, campaignService, logger, applicationSettings, cache, encryptionService) 27 | { 28 | _settingService = settingService; 29 | } 30 | 31 | [Route("settings/{countryCode}")] 32 | [Authorize(Roles = Constants.Admin)] 33 | public async Task Create(short countryCode) 34 | { 35 | ViewData["Countries"] = GetCountries(); 36 | var model = await GetSettings((Countries)countryCode); 37 | 38 | return View(model); 39 | } 40 | 41 | [HttpPost] 42 | [Route("settings/{countryCode}")] 43 | [Authorize(Roles = Constants.Admin)] 44 | public async Task Create(short countryCode, SettingsModel settingsModel) 45 | { 46 | ViewData["Countries"] = GetCountries(); 47 | settingsModel.Country = (Countries)countryCode; 48 | if (ModelState.IsValid) 49 | { 50 | await _settingService.Upsert(settingsModel); 51 | } 52 | else 53 | ModelState.AddModelError("", "Invalid configuration"); 54 | 55 | //To refresh the cache 56 | await _campaignService.GetCampaign((Countries)countryCode, true); 57 | 58 | return View(settingsModel); 59 | } 60 | 61 | private async Task GetSettings(Countries country) 62 | { 63 | var settings = await _settingService.GetAll(country); 64 | return new SettingsModel() 65 | { 66 | APISecret = settings.Where(x => x.Key == Constants.ClientSecret).Select(x => x.Value).FirstOrDefault(), 67 | StartDate = settings.Where(x => x.Key == Constants.SeasonStartDate).Select(x => Convert.ToDateTime(x.Value)).FirstOrDefault(), 68 | EndDate = settings.Where(x => x.Key == Constants.SeasonEndDateKey).Select(x => Convert.ToDateTime(x.Value)).FirstOrDefault(), 69 | Country = country, 70 | }; 71 | } 72 | 73 | private List GetCountries() 74 | { 75 | return Enum.GetValues(typeof(Countries)).Cast().Where(x => x != Countries.None).Select(v => new KeyValueModel() { Key = v.ToString(), Value = ((short)v).ToString() }).ToList(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/SecurityHeadersMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace BoilerPlate.Bootstrapper 4 | { 5 | public sealed class SecurityHeadersMiddleware 6 | { 7 | 8 | private const string PermissionPolicy = "accelerometer=();" + 9 | "ambient-light-sensor=();" + 10 | "autoplay=('self');" + 11 | "battery=();" + 12 | "camera=('self');" + 13 | "display-capture=();" + 14 | "document-domain=();" + 15 | "encrypted-media=();" + 16 | "execution-while-not-rendered=();" + 17 | "execution-while-out-of-viewport=();" + 18 | "gyroscope=();" + 19 | "magnetometer=();" + 20 | "microphone=('self');" + 21 | "midi=();" + 22 | "navigation-override=();" + 23 | "payment=();" + 24 | "picture-in-picture=();" + 25 | "publickey-credentials-get=();" + 26 | "sync-xhr=();" + 27 | "usb=();" + 28 | "wake-lock=();" + 29 | "xr-spatial-tracking=();"; 30 | 31 | private const string CSP = "base-uri 'none';" + 32 | "block-all-mixed-content;" + 33 | "child-src 'none';" + 34 | "connect-src 'self';" + 35 | "default-src 'self';" + 36 | "font-src 'self';" + 37 | "form-action 'self';" + 38 | "frame-ancestors 'none';" + 39 | "frame-src " + 40 | "img-src 'self' data:;" + 41 | "manifest-src 'none';" + 42 | "media-src 'self';" + 43 | "object-src 'self';" + 44 | "sandbox;" + 45 | "script-src 'self' 'unsafe-inline' ;" + 46 | "script-src-attr 'self';" + 47 | "script-src-elem 'self' 'unsafe-inline' ;" + 48 | "style-src 'self';" + 49 | "style-src-attr 'self' 'unsafe-inline';" + 50 | "style-src-elem 'self';" + 51 | "upgrade-insecure-requests;" + 52 | "worker-src 'none';"; 53 | 54 | private readonly RequestDelegate _next; 55 | 56 | public SecurityHeadersMiddleware(RequestDelegate next) 57 | { 58 | _next = next; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /BoilerPlate.Test/BrowserTestBase.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Support.UI; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace BoilerPlate.Test 9 | { 10 | [TestFixtureSource(nameof(BrowsersByDomain))] 11 | public abstract class BrowserTestBase : TestBase, IDisposable 12 | { 13 | private static readonly string[] Browsers = { "Chrome", "Edge", "Firefox" }; 14 | protected static readonly object[] BrowsersByDomain = Sites 15 | .SelectMany(site => Browsers.Select(browser => new TestConfig(site, browser))).ToArray(); 16 | 17 | protected IWebDriver Driver => Config.Driver; 18 | protected WebDriverWait Waiter(int seconds = 5) => new WebDriverWait(Driver, TimeSpan.FromSeconds(seconds)); 19 | 20 | protected BrowserTestBase(TestConfig config) : base(config) 21 | { 22 | } 23 | 24 | [SetUp] 25 | public void Setup() 26 | { 27 | Dispose(); 28 | Config.Driver = WebDriverFactory.Create(Config.BrowserName); 29 | Driver.Manage().Window.Maximize(); 30 | } 31 | 32 | [TearDown] 33 | public virtual void TearDown() 34 | { 35 | Dispose(); 36 | } 37 | 38 | 39 | #region Helpers 40 | protected void SetTextByName(string name, string value) 41 | { 42 | IWebElement element = Waiter().Until(x => x.FindElement(By.CssSelector($"[name = '{name}']"))); 43 | element.Clear(); 44 | element.SendKeys(value); 45 | } 46 | 47 | protected void SetTextById(string id, string value) 48 | { 49 | IWebElement element = Waiter().Until(x => x.FindElement(By.Id(id))); 50 | element.Clear(); 51 | element.SendKeys(value); 52 | } 53 | 54 | protected string GetTextByName(string name) 55 | { 56 | IWebElement element = Waiter().Until(x => x.FindElement(By.CssSelector($"[name = '{name}']"))); 57 | return element.Text; 58 | } 59 | 60 | protected IReadOnlyList GetByCssSelector(string selector) 61 | { 62 | IReadOnlyList elements = Waiter().Until(x => x.FindElements(By.CssSelector(selector))); 63 | return elements; 64 | } 65 | 66 | protected string GetTextById(string id) 67 | { 68 | IWebElement element = Waiter().Until(x => x.FindElement(By.Id(id))); 69 | return element.Text; 70 | } 71 | 72 | protected void ClickSubmitButton() 73 | { 74 | IWebElement submitButton = Waiter().Until(x => x.FindElement(By.CssSelector("[type = 'submit']"))); 75 | submitButton.Click(); 76 | } 77 | 78 | protected bool IsElementRendered(By by) 79 | { 80 | try 81 | { 82 | Waiter().Until(x => x.FindElement(by)); 83 | return true; 84 | } 85 | catch (Exception) 86 | { 87 | return false; 88 | } 89 | } 90 | #endregion 91 | 92 | public void Dispose() 93 | { 94 | if (Driver is not null) 95 | { 96 | Driver.Quit(); 97 | Driver.Dispose(); 98 | Config.Driver = null; 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /BoilerPlate.Test/UserTests/AdminTests.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using NUnit.Framework; 3 | using OpenQA.Selenium; 4 | using System.Linq; 5 | 6 | namespace BoilerPlate.Test 7 | { 8 | [Category("Admin")] 9 | public class AdminTests : BrowserTestBase 10 | { 11 | private const string AdminUsername = "admin"; 12 | private const string AdminPassword = "qwert123"; 13 | 14 | private AdminSignInPageObject _signinPageObject; 15 | 16 | public AdminTests(TestConfig config) : base(config) 17 | { 18 | } 19 | 20 | [SetUp] 21 | public new void Setup() 22 | { 23 | _signinPageObject = new AdminSignInPageObject(Driver, Config); 24 | _signinPageObject.GoToAdminPage(); 25 | } 26 | 27 | [TestCase] 28 | public void Admin_SignIn_HappyPath() 29 | { 30 | _signinPageObject.SetSingInPageFields(AdminUsername, AdminPassword); 31 | _signinPageObject.SubmitButton_Click(); 32 | 33 | Assert.AreEqual($"{Config.RootUrl}settings/{(short)Countries.UK}", Driver.Url); 34 | } 35 | 36 | [TestCase] 37 | public void Admin_SignIn_UserNameIsEmpty_ShouldNotBeSignedIn() 38 | { 39 | _signinPageObject.SetSingInPageFields(string.Empty, AdminPassword); 40 | _signinPageObject.SubmitButton_Click(); 41 | 42 | var validationMessage = GetTextById("UserName-error"); 43 | Assert.AreEqual(validationMessage, "The UserName field is required."); 44 | } 45 | 46 | [TestCase] 47 | public void Admin_SignIn_PasswordIsEmpty_ShouldNotBeSignedIn() 48 | { 49 | _signinPageObject.SetSingInPageFields(AdminUsername, string.Empty); 50 | _signinPageObject.SubmitButton_Click(); 51 | 52 | var validationMessage = GetTextById("Password-error"); 53 | Assert.AreEqual(validationMessage, "The Password field is required."); 54 | } 55 | 56 | [TestCase] 57 | public void Admin_SignIn_PasswordAndUserNameAreEmpty_ShouldNotBeSignedIn() 58 | { 59 | _signinPageObject.SetSingInPageFields(string.Empty, string.Empty); 60 | _signinPageObject.SubmitButton_Click(); 61 | 62 | var passwordValidationMessage = GetTextById("Password-error"); 63 | var userNameValidationMessage = GetTextById("UserName-error"); 64 | 65 | Assert.AreEqual(passwordValidationMessage, "The Password field is required."); 66 | Assert.AreEqual(userNameValidationMessage, "The UserName field is required."); 67 | } 68 | 69 | [TestCase] 70 | public void AnonymousUser_AccessHangfire_DashboardShouldNotBeDIsplayed() 71 | { 72 | Driver.Manage().Cookies.DeleteAllCookies(); 73 | Driver.Navigate().GoToUrl($"{Config.RootUrl}hangfire"); 74 | 75 | Assert.IsFalse(IsElementRendered(By.Id("hangfireConfig"))); 76 | } 77 | 78 | [TestCase] 79 | public void Admin_UserNameOrPasswordIsWrong_ShouldNotBeSignedIn() 80 | { 81 | var signInModel = new SignInModelFaker().Generate(); 82 | 83 | _signinPageObject.SetSingInPageFields(signInModel.UserName, signInModel.Password); 84 | _signinPageObject.SubmitButton_Click(); 85 | 86 | var validationMessages = Driver.FindElements(By.XPath("/html/body/div/main/div/div/form/div[1]/ul/li")); 87 | Assert.IsTrue(validationMessages.Any(x=>x.Text == "Invalid Login Attempt")); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /BoilerPlate.Data/Repositories/BaseRepository.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Utils; 4 | using Microsoft.EntityFrameworkCore; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Threading.Tasks; 10 | 11 | namespace BoilerPlate.Data 12 | { 13 | public class BaseRepository : IBaseRepository where T : BaseEntity 14 | { 15 | protected readonly ApplicationDataContext _dataContext; 16 | protected readonly ConnectionStrings _connectionStrings; 17 | 18 | 19 | public BaseRepository(ConnectionStrings connectionStrings, IDataContext dataContext) 20 | { 21 | _connectionStrings = connectionStrings; 22 | _dataContext = (ApplicationDataContext)dataContext; 23 | } 24 | 25 | public virtual async Task GetByIdAsync(TE id) 26 | { 27 | return await _dataContext.Set().FindAsync(id); 28 | } 29 | 30 | public virtual Task FirstOrDefaultAsync(Expression> predicate) 31 | { 32 | return _dataContext.Set().FirstOrDefaultAsyncNoLock(predicate); 33 | } 34 | 35 | public virtual async Task AnyAsync(Expression> predicate) 36 | { 37 | return await _dataContext.Set().AnyAsync(predicate); 38 | } 39 | 40 | public virtual async Task AddAsync(T entity) 41 | { 42 | await _dataContext.Set().AddAsync(entity); 43 | } 44 | 45 | public virtual async Task AddRangeAsync(IEnumerable entities) 46 | { 47 | await _dataContext.AddRangeAsync(entities.ToList()); 48 | return entities.Count(); 49 | } 50 | 51 | public virtual async Task UpdateAsync(T entity) 52 | { 53 | _dataContext.Set().Update(entity); 54 | } 55 | 56 | public virtual async Task UpdateRangeAsync(IEnumerable entities) 57 | { 58 | _dataContext.Set().UpdateRange(entities); 59 | } 60 | 61 | public virtual async Task CommitAsync() 62 | { 63 | return await _dataContext.SaveChangesAsync(); 64 | } 65 | 66 | public virtual Task RemoveAsync(T entity) 67 | { 68 | _dataContext.Set().Remove(entity); 69 | return _dataContext.SaveChangesAsync(); 70 | } 71 | 72 | public virtual async Task> GetAllAsync() 73 | { 74 | return await _dataContext.Set().ToListAsyncNoLock(); 75 | } 76 | 77 | public async Task> GetAsync(Expression> predicate, int count, int pageNumber) 78 | { 79 | return await _dataContext.Set().Where(predicate).Skip(count * (pageNumber - 1)).Take(count).ToListAsyncNoLock(); 80 | } 81 | 82 | public virtual async Task> GetWhereAsync(Expression> predicate) 83 | { 84 | var result = await _dataContext.Set().Where(predicate).ToListAsyncNoLock(); 85 | return result; 86 | } 87 | 88 | public virtual async Task CountAllAsync() 89 | { 90 | return await _dataContext.Set().CountAsyncNoLock(); 91 | } 92 | 93 | 94 | public virtual Task CountWhereAsync(Expression> predicate) 95 | { 96 | return _dataContext.Set().CountAsyncNoLock(predicate); 97 | } 98 | 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /BoilerPlate.Data/DBContext/DbExtensions.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using Microsoft.EntityFrameworkCore; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Threading.Tasks; 8 | using System.Transactions; 9 | 10 | namespace BoilerPlate.Data 11 | { 12 | public static class DbExtensions 13 | { 14 | public static async Task> ToListAsyncNoLock(this IQueryable query) 15 | { 16 | using (CreateNoLockTransaction()) 17 | { 18 | return await query.ToListAsync(); 19 | } 20 | } 21 | 22 | public static async Task FirstAsyncNoLock(this IQueryable query) 23 | { 24 | using (CreateNoLockTransaction()) 25 | { 26 | return await query.FirstAsync(); 27 | } 28 | } 29 | 30 | public static async Task FirstOrDefaultAsyncNoLock(this IQueryable query) 31 | { 32 | using (CreateNoLockTransaction()) 33 | { 34 | return await query.FirstOrDefaultAsync(); 35 | } 36 | } 37 | 38 | public static async Task LastOrDefaultAsyncNoLock(this IQueryable query) 39 | { 40 | using (CreateNoLockTransaction()) 41 | { 42 | return await query.LastOrDefaultAsync(); 43 | } 44 | } 45 | 46 | public static async Task SingleOrDefaultAsyncNoLock(this IQueryable query) 47 | { 48 | using (CreateNoLockTransaction()) 49 | { 50 | return await query.SingleOrDefaultAsync(); 51 | } 52 | } 53 | 54 | public static async Task AnyAsyncNoLock(this IQueryable query) 55 | { 56 | using (CreateNoLockTransaction()) 57 | { 58 | return await query.AnyAsync(); 59 | } 60 | } 61 | 62 | public static async Task CountAsyncNoLock(this IQueryable query) 63 | { 64 | using (CreateNoLockTransaction()) 65 | { 66 | return await query.CountAsync(); 67 | } 68 | } 69 | 70 | public static async Task CountAsyncNoLock(this IQueryable query, Expression> predicate) 71 | { 72 | using (CreateNoLockTransaction()) 73 | { 74 | return await query.CountAsync(predicate); 75 | } 76 | } 77 | 78 | public static async Task FirstOrDefaultAsyncNoLock(this IQueryable query, Expression> predicate) 79 | { 80 | using (CreateNoLockTransaction()) 81 | { 82 | return await query.FirstOrDefaultAsync(predicate); 83 | } 84 | 85 | } 86 | 87 | public static async Task ToArrayAsyncNoLock(this IQueryable query) 88 | { 89 | using (CreateNoLockTransaction()) 90 | { 91 | return await query.ToArrayAsync(); 92 | } 93 | } 94 | 95 | public static TransactionScope CreateNoLockTransaction() 96 | { 97 | return new TransactionScope( 98 | TransactionScopeOption.Required, 99 | new TransactionOptions 100 | { 101 | IsolationLevel = IsolationLevel.ReadUncommitted, 102 | }, TransactionScopeAsyncFlowOption.Enabled); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /BoilerPlate.Service/CampaignService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Entities; 2 | using BoilerPlate.Service.Contract; 3 | using BoilerPlate.Utils; 4 | using Microsoft.Extensions.Caching.Distributed; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Security.Cryptography; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace BoilerPlate.Service 15 | { 16 | public class CampaignService : ICampaignService 17 | { 18 | private readonly ISettingService _settingService; 19 | private readonly ICacheService _cacheService; 20 | 21 | public CampaignService(ISettingService settingService, 22 | ICacheService cacheService) 23 | { 24 | _cacheService = cacheService; 25 | _settingService = settingService; 26 | } 27 | 28 | public async Task GetCampaign(Countries country, bool purgeCache = false) 29 | { 30 | var cacheKey = $"{Constants.CampaignCacheKey}-{country}"; 31 | var campaignSettings = await _cacheService.Get(cacheKey, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(1), 32 | FetchCampaignDTO(country, purgeCache), 33 | purgeCache); 34 | 35 | return campaignSettings; 36 | } 37 | 38 | public async Task GetCampaign(bool purgeCache = false) 39 | { 40 | var country = SiteHelper.GetCountry(); 41 | var cacheKey = $"{Constants.CampaignCacheKey}-{country}"; 42 | 43 | var campaignSettings = await _cacheService.Get(cacheKey, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(1), 44 | FetchCampaignDTO(country, purgeCache), 45 | purgeCache); 46 | 47 | return campaignSettings; 48 | } 49 | 50 | public async Task> GetCampaigns(bool purgeCache = false) 51 | { 52 | var countries = EnumHelper.GetEnumList(); 53 | List model = new List(); 54 | 55 | foreach (var country in countries) 56 | { 57 | var campaign = await FetchCampaignDTO(country, purgeCache); 58 | model.Add(campaign); 59 | } 60 | 61 | return model; 62 | } 63 | 64 | private async Task FetchCampaignDTO(Countries country, bool purgeCache = false) 65 | { 66 | var campaignSettings = await _settingService.GetByGroupId(country, Constants.CampaignSettings, purgeCache); 67 | var now = DateTime.Now; 68 | 69 | Campaign campaign = new Campaign() 70 | { 71 | Country = country, 72 | Status = CampaignStatuses.ClosedForSeason 73 | }; 74 | 75 | if (campaignSettings is not null && campaignSettings.Count > 0) 76 | { 77 | campaign.SeasonEndDate = campaignSettings.Where(x => x.Key == Constants.SeasonEndDateKey).Select(x => x.Value).FirstOrDefault().TryConvertToDateTime(); 78 | campaign.SeasonStartDate = campaignSettings.Where(x => x.Key == Constants.SeasonStartDate).Select(x => x.Value).FirstOrDefault().TryConvertToDateTime(); 79 | 80 | if (!campaign.SeasonEndDate.HasValue || campaign.SeasonEndDate <= now) 81 | campaign.Status = CampaignStatuses.ClosedForSeason; 82 | 83 | else if (!campaign.SeasonStartDate.HasValue || campaign.SeasonStartDate >= now) 84 | campaign.Status = CampaignStatuses.ClosePreSeason; 85 | else 86 | campaign.Status = CampaignStatuses.Open; 87 | } 88 | 89 | return campaign; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /BoilerPlate.Data/DBContext/ModelBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Utils; 4 | using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Globalization; 12 | using System.Linq; 13 | 14 | namespace BoilerPlate.Data 15 | { 16 | public static class ModelBuilderExtensions 17 | { 18 | public static void Seed(this ModelBuilder modelBuilder) 19 | { 20 | CountrySeedData(modelBuilder); 21 | SettingSeedData(modelBuilder); 22 | } 23 | 24 | private static void SettingSeedData(ModelBuilder modelBuilder) 25 | { 26 | var countries = EnumHelper.GetEnumList(); 27 | List settings = new List(); 28 | var i = 1; 29 | foreach (var country in countries) 30 | { 31 | settings.Add(new Setting() 32 | { 33 | Id = Guid.NewGuid(), 34 | Status = 1, 35 | CountryId = (short)country, 36 | GroupId = Constants.CampaignSettings, 37 | Key = Constants.SeasonStartDate, 38 | Value = DateTime.UtcNow.ToString(), 39 | LastModifyDate = DateTime.Now, 40 | CreationDate = DateTime.Now 41 | }); 42 | 43 | settings.Add(new Setting() 44 | { 45 | Id = Guid.NewGuid(), 46 | Status = 1, 47 | CountryId = (short)country, 48 | GroupId = Constants.CampaignSettings, 49 | Key = Constants.SeasonEndDateKey, 50 | Value = DateTime.UtcNow.AddMonths(3).ToString(), 51 | LastModifyDate = DateTime.Now, 52 | CreationDate = DateTime.Now 53 | }); 54 | 55 | settings.Add(new Setting() 56 | { 57 | Id = Guid.NewGuid(), 58 | Status = 1, 59 | CountryId = (short)country, 60 | GroupId = Constants.CampaignSettings, 61 | Key = Constants.ClientSecret, 62 | Value = CountryAPISecrets.Where(x => x.Key == country).Select(x => x.Value).FirstOrDefault(), 63 | LastModifyDate = DateTime.Now, 64 | CreationDate = DateTime.Now 65 | }); 66 | } 67 | 68 | modelBuilder.Entity().HasData(settings); 69 | } 70 | 71 | private static void CountrySeedData(ModelBuilder modelBuilder) 72 | { 73 | var countries = EnumHelper.GetEnumList(); 74 | var countryEntities = new List(); 75 | foreach (var country in countries) 76 | { 77 | countryEntities.Add(new Country() 78 | { 79 | Id = (short)country, 80 | Name = country.ToString(), 81 | LastModifyDate = DateTime.Now, 82 | CreationDate = DateTime.Now 83 | }); 84 | } 85 | modelBuilder.Entity().HasData(countryEntities); 86 | } 87 | 88 | 89 | private static List> CountryAPISecrets = new List> 90 | { 91 | new KeyValuePair(Countries.UK,"a0d7d59a-526c-4b49-b351-4aef8e2d74d9"), 92 | new KeyValuePair(Countries.Au,"0592f2c9-af25-44f4-9486-0daa68aaf7b7") 93 | }; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /BoilerPlate.Test/Drivers/WebDriverFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Microsoft.Edge.SeleniumTools; 5 | using OpenQA.Selenium; 6 | using OpenQA.Selenium.Chrome; 7 | using OpenQA.Selenium.Firefox; 8 | using OpenQA.Selenium.IE; 9 | using OpenQA.Selenium.Remote; 10 | using WebDriverManager.DriverConfigs.Impl; 11 | 12 | namespace BoilerPlate.Test 13 | { 14 | public class WebDriverFactory 15 | { 16 | public static IWebDriver Create(string browserName) 17 | { 18 | bool isDebuggerAttached = System.Diagnostics.Debugger.IsAttached; 19 | return browserName.ToLowerInvariant() switch 20 | { 21 | "chrome" => CreateChromeDriver(isDebuggerAttached), 22 | "edge" => CreateEdgeDriver(isDebuggerAttached), 23 | "firefox" => CreateFirefoxDriver(isDebuggerAttached), 24 | "internetexplorer" => CreateExplorerDriver(isDebuggerAttached), 25 | _ => throw new NotSupportedException($"The browser '{browserName}' is not supported."), 26 | }; 27 | } 28 | 29 | private static IWebDriver CreateChromeDriver(bool isDebuggerAttached) 30 | { 31 | var driver = new WebDriverManager.DriverManager().SetUpDriver(new ChromeConfig()); 32 | var driverDirectory = System.IO.Path.GetDirectoryName(driver) ?? "."; 33 | var options = new ChromeOptions(); 34 | var tmp = Path.GetTempFileName() + "dir"; 35 | Directory.CreateDirectory(tmp); 36 | 37 | options.AddArguments("user-data-dir=" + tmp.Replace("\\", "/")); 38 | options.AddArgument("--window-size=1920,1080"); 39 | options.AddArgument("--headless"); 40 | 41 | if (!isDebuggerAttached) 42 | { 43 | options.AddArgument("--headless"); 44 | } 45 | 46 | // HACK Workaround for "(unknown error: DevToolsActivePort file doesn't exist)" 47 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 48 | options.AddArgument("--no-sandbox"); 49 | 50 | return new ChromeDriver(driverDirectory, options); 51 | } 52 | 53 | private static IWebDriver CreateEdgeDriver(bool isDebuggerAttached) 54 | { 55 | var driver = new WebDriverManager.DriverManager().SetUpDriver(new EdgeConfig()); 56 | var driverDirectory = System.IO.Path.GetDirectoryName(driver) ?? "."; 57 | 58 | var options = new EdgeOptions() 59 | { 60 | UseChromium = true, 61 | }; 62 | 63 | if (!isDebuggerAttached) 64 | options.AddArgument("--headless"); 65 | 66 | 67 | return new EdgeDriver(driverDirectory, options); 68 | } 69 | 70 | private static IWebDriver CreateFirefoxDriver(bool isDebuggerAttached) 71 | { 72 | FirefoxOptions firefoxOptions = new FirefoxOptions(); 73 | firefoxOptions.AcceptInsecureCertificates = true; 74 | 75 | if (!isDebuggerAttached) 76 | firefoxOptions.AddArgument("--headless"); 77 | 78 | var driver = new WebDriverManager.DriverManager().SetUpDriver(new FirefoxConfig()); 79 | var driverDirectory = System.IO.Path.GetDirectoryName(driver) ?? "."; 80 | 81 | return new FirefoxDriver(driverDirectory, firefoxOptions, TimeSpan.FromSeconds(20)); 82 | } 83 | 84 | private static IWebDriver CreateExplorerDriver(bool isDebuggerAttached) 85 | { 86 | var driver = new WebDriverManager.DriverManager().SetUpDriver(new InternetExplorerConfig()); 87 | var driverDirectory = Path.GetDirectoryName(driver) ?? "."; 88 | 89 | return new InternetExplorerDriver(driverDirectory, new InternetExplorerOptions {IgnoreZoomLevel = true}); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /BoilerPlate.Web/Controllers/OrderController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Routing; 6 | using Microsoft.Extensions.Caching.Distributed; 7 | using Microsoft.Extensions.Logging; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using BoilerPlate.Service.Contract; 12 | using BoilerPlate.Utils; 13 | using BoilerPlate.Bootstrapper; 14 | using BoilerPlate.Web.Entities; 15 | 16 | namespace BoilerPlate.Web.Controllers 17 | { 18 | public class OrderController : BaseController 19 | { 20 | private readonly IIdentityService _identityService; 21 | protected override Market MarketConfiguration => base.MarketConfiguration; 22 | 23 | public OrderController(UserManager userManager, 24 | ApplicationSettings applicationSettings, 25 | ICampaignService campaignService, 26 | IIdentityService identityService, 27 | IDistributedCache cache, 28 | IEncryptionService encryptionService, 29 | ILogger logger) : base(userManager, campaignService, logger, applicationSettings, cache, encryptionService) 30 | { 31 | _identityService = identityService; 32 | } 33 | 34 | [HttpGet] 35 | [APIUserAuthorize] 36 | [Route("api/orders")] 37 | [ProducesResponseType(StatusCodes.Status200OK)] 38 | [ProducesResponseType(StatusCodes.Status400BadRequest)] 39 | [ProducesResponseType(StatusCodes.Status401Unauthorized)] 40 | [ApiExplorerSettings(IgnoreApi = false)] 41 | public async Task> GetFoundOrders() 42 | { 43 | return await GetFoundOrders(new ListRequestModel() { PageItemCount = 100, PageNumber = 1 }); 44 | } 45 | 46 | [HttpGet] 47 | [APIUserAuthorize] 48 | [Route("api/orders/{pageNumber}/{PageItemCount}")] 49 | [ProducesResponseType(StatusCodes.Status200OK)] 50 | [ProducesResponseType(StatusCodes.Status400BadRequest)] 51 | [ProducesResponseType(StatusCodes.Status401Unauthorized)] 52 | [ApiExplorerSettings(IgnoreApi = false)] 53 | public async Task> GetFoundOrders([FromRoute] ListRequestModel listModel) 54 | { 55 | throw new NotImplementedException(); 56 | } 57 | 58 | [HttpPost] 59 | [APIUserAuthorize] 60 | [Route("api/orders")] 61 | [ProducesResponseType(StatusCodes.Status200OK)] 62 | [ProducesResponseType(StatusCodes.Status400BadRequest)] 63 | [ProducesResponseType(StatusCodes.Status401Unauthorized)] 64 | [ApiExplorerSettings(IgnoreApi = false)] 65 | public async Task Create([FromBody] CreateOrderRequestModel createOrderRequestModel) 66 | { 67 | throw new NotImplementedException(); 68 | } 69 | 70 | [HttpPut] 71 | [APIUserAuthorize] 72 | [Route("api/orders/{orderId}")] 73 | [ProducesResponseType(StatusCodes.Status200OK)] 74 | [ProducesResponseType(StatusCodes.Status400BadRequest)] 75 | [ProducesResponseType(StatusCodes.Status401Unauthorized)] 76 | [ApiExplorerSettings(IgnoreApi = false)] 77 | public async Task Update(string orderId, [FromBody] UpdateOrderRequestModel updateOrderRequestModel) 78 | { 79 | throw new NotImplementedException(); 80 | } 81 | 82 | [HttpDelete] 83 | [APIUserAuthorize] 84 | [Route("api/orders/{orderId}")] 85 | [ProducesResponseType(StatusCodes.Status200OK)] 86 | [ProducesResponseType(StatusCodes.Status400BadRequest)] 87 | [ProducesResponseType(StatusCodes.Status401Unauthorized)] 88 | [ApiExplorerSettings(IgnoreApi = false)] 89 | public async Task DeleteByOrderNumber(string orderId) 90 | { 91 | throw new NotImplementedException(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /BoilerPlate.Service/EncryptionService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service.Contract; 2 | using BoilerPlate.Utils; 3 | using System; 4 | using System.IO; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | 8 | namespace BoilerPlate.Service 9 | { 10 | public class EncryptionService : IEncryptionService 11 | { 12 | static readonly char[] padding = { '=' }; 13 | public EncryptionService() 14 | { 15 | } 16 | 17 | public string Encrypt(string input) 18 | { 19 | var enc = EncryptStringToBytes(input); 20 | return enc; 21 | } 22 | 23 | public string Encrypt(int input) 24 | { 25 | return Encrypt(input.ToString()); 26 | } 27 | 28 | public string Decrypt(string cipherText) 29 | { 30 | return DecryptString(cipherText); 31 | } 32 | 33 | public T Decrypt(string cipherText) where T : struct 34 | { 35 | return (T)Convert.ChangeType(DecryptString(cipherText), typeof(T)); 36 | } 37 | 38 | private static string EncryptStringToBytes(string plainText) 39 | { 40 | try 41 | { 42 | 43 | byte[] clearBytes = Encoding.Unicode.GetBytes(plainText); 44 | using (Aes encryptor = Aes.Create()) 45 | { 46 | Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Constants.EncryptionSymetrickey, Constants.EncryptionIv); 47 | encryptor.Key = pdb.GetBytes(32); 48 | encryptor.IV = pdb.GetBytes(16); 49 | using (MemoryStream ms = new MemoryStream()) 50 | { 51 | using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) 52 | { 53 | cs.Write(clearBytes, 0, clearBytes.Length); 54 | cs.Close(); 55 | } 56 | plainText = Convert.ToBase64String(ms.ToArray()).TrimEnd(padding).Replace('+', '-').Replace('/', '_'); 57 | } 58 | } 59 | return plainText; 60 | } 61 | catch 62 | { 63 | 64 | } 65 | return ""; 66 | } 67 | 68 | private static string DecryptString(string cipherText) 69 | { 70 | if (!string.IsNullOrEmpty(cipherText)) 71 | { 72 | string incoming = cipherText.Replace('_', '/').Replace('-', '+'); 73 | switch (cipherText.Length % 4) 74 | { 75 | case 2: incoming += "=="; break; 76 | case 3: incoming += "="; break; 77 | } 78 | try 79 | { 80 | byte[] cipherBytes = Convert.FromBase64String(incoming); 81 | using (Aes encryptor = Aes.Create()) 82 | { 83 | Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Constants.EncryptionSymetrickey, Constants.EncryptionIv); 84 | encryptor.Key = pdb.GetBytes(32); 85 | encryptor.IV = pdb.GetBytes(16); 86 | using (MemoryStream ms = new MemoryStream()) 87 | { 88 | using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write)) 89 | { 90 | cs.Write(cipherBytes, 0, cipherBytes.Length); 91 | cs.Close(); 92 | } 93 | cipherText = Encoding.Unicode.GetString(ms.ToArray()); 94 | } 95 | } 96 | return cipherText; 97 | } 98 | catch 99 | { 100 | 101 | } 102 | } 103 | 104 | return ""; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ServiceExtensions/RepositoryServiceExtension.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Utils; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System.Threading.Tasks; 6 | using BoilerPlate.Data.Contract; 7 | using BoilerPlate.Data; 8 | using System; 9 | 10 | namespace BoilerPlate.Bootstrapper 11 | { 12 | public static class RepositoryServiceExtension 13 | { 14 | public static IServiceCollection AddRepositoryServices(this IServiceCollection services, ConnectionStrings connectionStrings) 15 | { 16 | services.AddTransient(); 17 | services.AddTransient(); 18 | 19 | //services.AddIdentityCore(config => 20 | //{ 21 | // config.SignIn.RequireConfirmedEmail = true; 22 | // config.Tokens.ProviderMap.Add("CustomEmailConfirmation", new TokenProviderDescriptor(typeof(CustomEmailConfirmationTokenProvider))); 23 | // config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation"; 24 | 25 | //}).AddRoles().AddEntityFrameworkStores(); 26 | 27 | 28 | // services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true).AddRoles() 29 | //.AddEntityFrameworkStores(); 30 | 31 | //services.AddDefaultIdentity(config => 32 | //{ 33 | // config.SignIn.RequireConfirmedEmail = true; 34 | // config.Tokens.ProviderMap.Add("CustomEmailConfirmation", new TokenProviderDescriptor(typeof(CustomEmailConfirmationTokenProvider))); 35 | // config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation"; 36 | 37 | //}).AddRoles().AddEntityFrameworkStores(); 38 | //////Identity Settings 39 | services.AddIdentity(config => 40 | { 41 | config.SignIn.RequireConfirmedEmail = false; 42 | config.Tokens.ProviderMap.Add("CustomEmailConfirmation", new TokenProviderDescriptor(typeof(CustomEmailConfirmationTokenProvider))); 43 | config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation"; 44 | 45 | }).AddRoles().AddEntityFrameworkStores().AddDefaultTokenProviders(); 46 | 47 | //Cookie Settings 48 | services.ConfigureApplicationCookie(options => 49 | { 50 | options.ExpireTimeSpan = Constants.CookieExpiration; 51 | options.SlidingExpiration = false; 52 | options.LoginPath = "/signup"; 53 | options.LogoutPath = "/signout"; 54 | options.AccessDeniedPath = "/error/403"; 55 | }); 56 | 57 | services.AddScoped, AppClaimsPrincipalFactory>(); 58 | 59 | services.AddTransient>(); 60 | 61 | services.AddDbContext(options => 62 | options.UseSqlServer(connectionStrings.MainConnection), ServiceLifetime.Transient); 63 | 64 | 65 | services.AddTransient(typeof(IBaseRepository<,>), typeof(BaseRepository<,>)); 66 | services.AddTransient(); 67 | services.AddTransient(); 68 | 69 | services.AddDistributedSqlServerCache(options => 70 | { 71 | options.ConnectionString = 72 | connectionStrings.MainConnection; 73 | options.SchemaName = "dbo"; 74 | options.TableName = "Cache"; 75 | }); 76 | 77 | var dbContext = services.BuildServiceProvider().GetService(); 78 | dbContext.EnsureDbCreated(); 79 | 80 | return services; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /BoilerPlate.Web/Controllers/BaseController.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service.Contract; 2 | using BoilerPlate.Utils; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Caching.Distributed; 7 | using Microsoft.Extensions.Logging; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Diagnostics; 11 | using System.Globalization; 12 | using System.Linq; 13 | using System.Threading.Tasks; 14 | 15 | namespace BoilerPlate.Web.Controllers 16 | { 17 | [ApiExplorerSettings(IgnoreApi = true)] 18 | public class BaseController : Controller 19 | { 20 | private Market _market; 21 | private ApplicationUser _user; 22 | private Campaign _campaign; 23 | 24 | protected readonly ILogger _logger; 25 | protected readonly UserManager _userManager; 26 | protected readonly IDistributedCache _cache; 27 | protected readonly ApplicationSettings _applicationSettings; 28 | protected readonly IEncryptionService _encryptionService; 29 | protected readonly ICampaignService _campaignService; 30 | 31 | public BaseController(UserManager userManager, 32 | ICampaignService campaignService, 33 | ILogger logger, 34 | ApplicationSettings applicationSettings, 35 | IDistributedCache cache, 36 | IEncryptionService encryptionService) => 37 | (_logger, _applicationSettings, _userManager, _campaignService, _cache, _encryptionService) = 38 | (logger, applicationSettings, userManager, campaignService, cache, encryptionService); 39 | 40 | 41 | protected ApplicationUser ApplicationUser 42 | { 43 | get 44 | { 45 | if (_user == null) 46 | InitializeUser(); 47 | return _user; 48 | } 49 | } 50 | 51 | protected virtual Market MarketConfiguration 52 | { 53 | get 54 | { 55 | if (_market == null) 56 | InitializeMarket(); 57 | return _market; 58 | } 59 | } 60 | 61 | protected Campaign Campaign 62 | { 63 | get 64 | { 65 | if (_campaign == null) 66 | InitializeCampaign(); 67 | return _campaign; 68 | } 69 | } 70 | 71 | private void InitializeUser() 72 | { 73 | if (HttpContext?.User?.Identity?.IsAuthenticated ?? false) 74 | _user = _userManager.GetUserAsync(HttpContext.User).Result; 75 | } 76 | 77 | private void InitializeMarket() 78 | { 79 | var currentCulture = CultureInfo.CurrentUICulture; 80 | if (currentCulture is not null) 81 | { 82 | var country = EnumHelper.GetValueFromDescription(currentCulture.Name); 83 | _market = new Market() 84 | { 85 | Campaign = this.Campaign, 86 | Country = country, 87 | Host = GetHost(country) 88 | }; 89 | } 90 | } 91 | 92 | private void InitializeCampaign() 93 | { 94 | var currentCulture = CultureInfo.CurrentUICulture; 95 | if (currentCulture is not null) 96 | { 97 | var country = EnumHelper.GetValueFromDescription(currentCulture.Name); 98 | _campaign = _campaignService.GetCampaign(country).Result; 99 | } 100 | } 101 | 102 | protected string GetHost(Countries country) 103 | { 104 | return SiteHelper.GetHost(_applicationSettings, country); 105 | } 106 | 107 | protected string GetCookie(string key) 108 | { 109 | return Request.Cookies[key]; 110 | } 111 | 112 | public void SetCookie(string key, string value, TimeSpan expireDate) 113 | { 114 | CookieOptions option = new CookieOptions(); 115 | option.Expires = DateTime.Now.Add(expireDate); 116 | Response.Cookies.Append(key, value, option); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /BoilerPlate.Service/SettingService.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Data.Contract; 2 | using BoilerPlate.Data.Entities; 3 | using BoilerPlate.Service.Contract; 4 | using BoilerPlate.Utils; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Security.Cryptography; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using System.Linq; 14 | using BoilerPlate.Web.Entities; 15 | 16 | namespace BoilerPlate.Service 17 | { 18 | public class SettingService : ISettingService 19 | { 20 | public readonly ISettingRepository _settingRepository; 21 | private readonly ICacheService _cacheService; 22 | 23 | public SettingService(ISettingRepository settingRepository, ICacheService cacheService) 24 | { 25 | _settingRepository = settingRepository; 26 | _cacheService = cacheService; 27 | } 28 | 29 | public async Task> GetByGroupId(Countries country, int groupId, bool purge = false) 30 | { 31 | var cacheKey = $"SettingGroup-{(short)country}-{groupId}"; 32 | return await _cacheService.Get(cacheKey, TimeSpan.FromMinutes(1), 33 | _settingRepository.GetByGroupId((short)country, groupId), 34 | purge); 35 | } 36 | 37 | public async Task GetByKey(Countries country, string key, bool purge = false) 38 | { 39 | var cacheKey = $"{(short)country}-{key}"; 40 | return await _cacheService.Get(cacheKey, TimeSpan.FromMinutes(10), 41 | _settingRepository.GetByKey((short)country, key), 42 | purge); 43 | } 44 | 45 | public async Task GetByValue(Countries country, string value, bool purge = false) 46 | { 47 | return await _settingRepository.GetByValue(value, (short)country); 48 | } 49 | 50 | public async Task ValueExists(Countries country, string value) 51 | { 52 | return await _settingRepository.AnyAsync(x => x.CountryId == (short)country && x.Value == value); 53 | } 54 | 55 | public async Task GetByKeyValue(string key, string value, bool purge = false) 56 | { 57 | var cacheKey = $"{key}-{value}"; 58 | return await _cacheService.Get(cacheKey, TimeSpan.FromMinutes(10), 59 | _settingRepository.FirstOrDefaultAsync(x => x.Value == value && x.Key == key), 60 | purge); 61 | } 62 | 63 | public async Task> GetAll(Countries country) 64 | { 65 | var settings = await _settingRepository.GetWhereAsync(x => x.CountryId == (short)country); 66 | if (settings != null) 67 | return settings.ToList(); 68 | return null; 69 | } 70 | 71 | public async Task Upsert(Countries country, string key, string value, short? groupId) 72 | { 73 | var entity = await _settingRepository.GetByKey((short)country, key); 74 | 75 | if (entity != null) 76 | { 77 | entity.Value = value; 78 | entity.LastModifyDate = DateTime.Now; 79 | await _settingRepository.UpdateAsync(entity); 80 | await _settingRepository.CommitAsync(); 81 | } 82 | else 83 | { 84 | await _settingRepository.AddAsync(new Setting() 85 | { 86 | Key = key, 87 | Value = value, 88 | Status = 1, 89 | GroupId = groupId, 90 | CountryId = (short)country, 91 | CreationDate = DateTime.Now, 92 | LastModifyDate = DateTime.Now 93 | }); 94 | await _settingRepository.CommitAsync(); 95 | } 96 | } 97 | 98 | //TODO:Refactor it 99 | public async Task Upsert(SettingsModel model) 100 | { 101 | await Upsert(model.Country, Constants.ClientSecret, model.APISecret, null); 102 | await Upsert(model.Country, Constants.SeasonStartDate, model.StartDate.ToString(), null); 103 | await Upsert(model.Country, Constants.SeasonEndDateKey, model.EndDate.ToString(), null); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /BoilerPlate.Utils/Helpers/ApiCaller/RequestHelpers.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Text; 8 | 9 | namespace BoilerPlate.Utils 10 | { 11 | public static class RequestHelpers 12 | { 13 | public static string ToQueryString(this object obj) 14 | { 15 | if (obj == null) 16 | return string.Empty; 17 | 18 | var builder = new StringBuilder("?"); 19 | var objType = obj.GetType(); 20 | 21 | var parameters = objType.GetProperties() 22 | .Select(p => 23 | new 24 | { 25 | Param = (Attribute.IsDefined(p, typeof(DescriptionAttribute)) ? 26 | (Attribute.GetCustomAttribute(p, typeof(DescriptionAttribute)) as DescriptionAttribute).Description 27 | : p.Name), 28 | Value = Uri.EscapeDataString(p.GetValue(obj).ToString()) 29 | }) 30 | .ToList(); 31 | 32 | return builder.Append(string.Join("&", parameters.Select(x => $"{x.Param}={x.Value}").ToArray())).ToString(); 33 | } 34 | 35 | public static Uri GetRequestUrl(string url, string methodName, string queryString = "") 36 | { 37 | var requestUrl = $"{url}{methodName}{queryString}"; 38 | return new Uri(requestUrl); 39 | } 40 | 41 | public static ByteArrayContent CreateRequestBodyContent(object requestObject, ContentType contentType) 42 | { 43 | ByteArrayContent content = null; 44 | 45 | switch (contentType) 46 | { 47 | case ContentType.None: 48 | break; 49 | case ContentType.ApplicationJson: 50 | content = new StringContent(JsonConvert.SerializeObject(requestObject), Encoding.UTF8, contentType.GetDescription()); 51 | break; 52 | case ContentType.Text: 53 | content = new StringContent(requestObject.ToTextPlain(), Encoding.UTF8, contentType.GetDescription()); 54 | break; 55 | case ContentType.FormData: 56 | content = new FormUrlEncodedContent(requestObject.ToFormData()); 57 | break; 58 | default: 59 | break; 60 | } 61 | 62 | return content; 63 | } 64 | 65 | public static string ToTextPlain(this object obj) 66 | { 67 | if (obj == null) 68 | return string.Empty; 69 | 70 | var builder = new StringBuilder(); 71 | var objType = obj.GetType(); 72 | 73 | var parameters = objType.GetProperties() 74 | .Select(p => 75 | new 76 | { 77 | Param = (Attribute.IsDefined(p, typeof(DescriptionAttribute)) ? 78 | (Attribute.GetCustomAttribute(p, typeof(DescriptionAttribute)) as DescriptionAttribute).Description 79 | : p.Name), 80 | Value = Uri.EscapeDataString(p.GetValue(obj).ToString()) 81 | }) 82 | .ToList(); 83 | 84 | return builder.Append(string.Join("&", parameters.Select(x => $"{x.Param}={x.Value}").ToArray())).ToString(); 85 | } 86 | 87 | public static List> ToFormData(this object obj) 88 | { 89 | if (obj == null) 90 | return null; 91 | 92 | var builder = new StringBuilder(); 93 | var objType = obj.GetType(); 94 | 95 | var param = objType.GetProperties() 96 | .Select(p => 97 | new KeyValuePair( 98 | (Attribute.IsDefined(p, typeof(DescriptionAttribute)) ? 99 | (Attribute.GetCustomAttribute(p, typeof(DescriptionAttribute)) as DescriptionAttribute).Description 100 | : p.Name), 101 | p.GetValue(obj).ToString())) 102 | .ToList(); 103 | 104 | return param; 105 | } 106 | 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /BoilerPlate.Bootstrapper/ResponseMiddleware.cs: -------------------------------------------------------------------------------- 1 | using BoilerPlate.Service.Contract; 2 | using BoilerPlate.Utils; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | using Newtonsoft.Json; 8 | using System; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using System.Web; 12 | 13 | namespace BoilerPlate.Bootstrapper 14 | { 15 | public class ResponseMiddleware 16 | { 17 | private readonly RequestDelegate _next; 18 | 19 | public ResponseMiddleware(RequestDelegate next) 20 | { 21 | _next = next; 22 | } 23 | 24 | public async Task InvokeAsync(HttpContext context, IIdentityService identityService, ILogger logger, IWebHostEnvironment env, ApplicationSettings applicationSettings) 25 | { 26 | string requestId = GetRequestId(context); 27 | context.Items["CorrelationId"] = requestId; 28 | 29 | if (context.Request.Path.Value.Contains("/api/orders", StringComparison.InvariantCultureIgnoreCase) 30 | || context.Request.QueryString.Value.Contains(HttpUtility.UrlEncode("/api/orders"), StringComparison.InvariantCultureIgnoreCase)) 31 | { 32 | var token = context.Request.Headers["Authorization"].FirstOrDefault(); 33 | var user = await identityService.AuthenticateAPIUser(token); 34 | if ((token == null || user == null) && context.Request.Path != "/api/orders/associate") 35 | { 36 | var authenticationException = new AuthenticationFailedException("Authentication error!"); 37 | logger.LogError(authenticationException, "{Message}{RequestId}", $"{context.Connection.RemoteIpAddress} - {authenticationException.Message}", requestId); 38 | context.Response.Clear(); 39 | await HandleExceptionAsync(context, authenticationException, requestId); 40 | } 41 | else 42 | { 43 | context.Items["User"] = user; 44 | await Next(context, logger, env, requestId); 45 | } 46 | 47 | } 48 | 49 | else 50 | await Next(context, logger, env, requestId); 51 | } 52 | 53 | private async Task Next(HttpContext context, ILogger logger, IWebHostEnvironment env, string requestId) 54 | { 55 | if (env.IsDevelopment()) 56 | await _next(context); 57 | else 58 | { 59 | try 60 | { 61 | await _next(context); 62 | 63 | if (!(context.Request.Path.Value.Contains("/api") || context.Request.IsAjaxRequest()) && 64 | context.Response.StatusCode == 404) 65 | context.Response.Redirect("/page-not-found"); 66 | } 67 | catch (CustomException ex) 68 | { 69 | logger.LogError(ex, "{Message}{RequestId}", ex.Message, requestId); 70 | 71 | if (context.Request.Path.Value.Contains("/api") || context.Request.IsAjaxRequest()) 72 | await HandleExceptionAsync(context, ex, requestId); 73 | else 74 | context.Response.Redirect($"/error/{requestId}"); 75 | } 76 | catch (Exception ex) 77 | { 78 | logger.LogError(ex, "{Message}{RequestId}", ex.Message, requestId); 79 | 80 | if (context.Request.Path.Value.Contains("/api") || context.Request.IsAjaxRequest()) 81 | await HandleExceptionAsync(context, ex, requestId); 82 | else 83 | context.Response.Redirect($"/error/{requestId}"); 84 | } 85 | } 86 | } 87 | 88 | private static Task HandleExceptionAsync(HttpContext context, Exception exception, string correlationId) 89 | { 90 | context.Response.Clear(); 91 | var apiResponse = new APIErrorResponseWrapper(); 92 | var apiError = ApiErrorResponseFactory.Create(exception); 93 | apiError.CorrelationId = correlationId; 94 | context.Response.StatusCode = apiError.StatusCode; 95 | context.Response.ContentType = "application/json"; 96 | apiResponse.Error = apiError; 97 | 98 | var json = JsonConvert.SerializeObject(apiResponse); 99 | 100 | return context.Response.WriteAsync(json); 101 | } 102 | 103 | private static string GetRequestId(HttpContext context) 104 | { 105 | string requestId; 106 | var header = context.Request.Headers["CorrelationId"]; 107 | if (header.Count > 0) 108 | requestId = header[0]; 109 | else 110 | requestId = Guid.NewGuid().ToString(); 111 | 112 | return requestId; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET Core 6.x MVC Boiler plate 2 | 3 | An ASP.NET Core 6.x MVC project template for a quick start. 4 | 5 | Versions: 6 | Framework .NET 6.x 7 | Language C# 10 8 | 9 | # Functionalities / Features 10 | 11 |
    12 |
  • 13 | User/Role Management with ASP.NET Identity 14 |
  • 15 |
  • 16 | Hangfire 17 |
      Dashboard
    18 |
      Authorization
    19 |
      Custom Queue/Worker management
    20 |
  • 21 |
  • 22 | Swagger 23 |
      API Documentation
    24 |
  • 25 |
  • 26 | Logging with Serilog 27 |
      SQL Implementation
    28 |
      Custom RequestId
    29 |
  • 30 |
  • 31 | Admin Panel 32 |
      Campaign Management
    33 |
      API Credential Management
    34 |
      Hangfire Dashboard Access
    35 |
  • 36 |
  • 37 | Campaign Management 38 |
      Campaign State Management by market (Open / PreSeason / PostSeason)
    39 |
  • 40 |
  • 41 | Response Caching 42 |
  • 43 |
  • 44 | Markup Minification 45 |
  • 46 |
  • 47 | Response Compression 48 |
  • 49 |
  • 50 | Error Handling 51 |
  • 52 |
  • 53 | Generic Repository 54 |
  • 55 |
  • 56 | Code First 57 |
  • 58 |
  • 59 | Generic Response Wrapper as a Middleware 60 |
  • 61 |
  • 62 | RESTful API support 63 |
  • 64 |
  • 65 | Routing 66 |
  • 67 |
  • 68 | Cache Services 69 |
  • 70 |
  • 71 | Globalization and Localization 72 |
      Determining the culture by the URL or a route parameter
    73 |
  • 74 |
  • 75 | Site Settings Management 76 |
  • 77 |
  • 78 | Encyription 79 |
  • 80 |
  • 81 | Automated Tests 82 |
      A scalable test automation infrastructure with Selenium + NUnit + Bogus
    83 |
      Fakers
    84 |
      User Tests
    85 |
      Browser drivers (Chrome, Edge, Firefox)
    86 |
  • 87 |
88 | 89 | # How to run on your local machine? 90 | You just need to replace the connection strings for the main and hangfire in appsettings.json with yours. Check the user privileges in the connection strings, the user should be able to create a database. 91 | 92 | ![image](https://user-images.githubusercontent.com/1719611/116728657-1845e600-a9de-11eb-8200-45b84251a4e5.png) 93 | 94 | 95 | IIS Express should be chosen from the launch configuration drop down. 96 | 97 | ![image](https://user-images.githubusercontent.com/1719611/116729510-2811fa00-a9df-11eb-8239-bcbb8529ccec.png) 98 | 99 | 100 | If you need to work with multiple environments then replace the connection strings in equivalent appsettings.{env}.json. 101 | 102 | ![image](https://user-images.githubusercontent.com/1719611/116729709-660f1e00-a9df-11eb-9620-b711dd73ce70.png) 103 | 104 | 105 | # Layers and Structure 106 | 107 | ![image](https://user-images.githubusercontent.com/1719611/116734556-4e3a9880-a9e5-11eb-8e94-259bc1aa38bb.png) 108 | 109 | 110 | # 1-Data 111 | 112 | Contains only database CRUD operations via a generic repository. 113 |
    114 |
  • 115 | BoilerPlate.Data 116 |
      Data Contexts
    117 |
      Repositories
    118 |
      Extensions
    119 |
  • 120 |
  • 121 | BoilerPlate.Data.Contracts 122 |
      Contracts
    123 |
  • 124 |
  • 125 | BoilerPlate.Data.Entities 126 |
      Database Objects
    127 |
  • 128 |
129 | 130 | # 2-Service 131 | 132 | All business services, contracts and entities are in this layer. 133 | Contains only database CRUD operations via a generic repository. 134 |
    135 |
  • 136 | BoilerPlate.Service 137 |
      Business Services
    138 |
  • 139 |
  • 140 | BoilerPlate.Service.Contracts 141 |
      Contracts
    142 |
  • 143 |
  • 144 | BoilerPlate.Service.Entities 145 |
      Business Objects
    146 |
  • 147 |
148 | 149 | # 3-Web 150 | 151 | Web application and it's entities place in this layer. 152 |
    153 |
  • 154 | BoilerPlate.Web 155 |
      MVC Application
    156 |
  • 157 |
  • 158 | BoilerPlate.Service.Contracts 159 |
      Contracts
    160 |
  • 161 |
  • 162 | BoilerPlate.Web.Entities 163 |
      Models and API request/response models
    164 |
  • 165 |
166 | 167 | # 4-Test 168 | 169 | Test automations with Selenium + NUnit + Bogus 170 | 171 |
    172 |
  • 173 | BoilerPlate.Test 174 |
      Drivers
    175 |
      Fakers
    176 |
      PageObjects
    177 |
      UserTests
    178 |
179 | 180 | # 5-Common 181 |
    182 |
  • 183 | BoilerPlate.Bootstrapper 184 |
      Dependencies
    185 |
      Middlewares
    186 |
      Attributes
    187 |
      Localization
    188 |
  • 189 |
  • 190 | BoilerPlate.Utils 191 |
      Configuration
    192 |
      Constants
    193 |
      Enums
    194 |
      Exceptions
    195 |
      Extensions
    196 |
      Helpers
    197 |
  • 198 |
  • 199 | BoilerPlate.Web.Entities 200 |
      View Models
    201 |
      API Models
    202 |
  • 203 |
204 | 205 | 206 | --------------------------------------------------------------------------------