Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ csharp_preserve_single_line_blocks = true
# IntelliTect Conventions #
###############################
# var preferences
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_for_built_in_types = false:none
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = false:warning
csharp_style_var_elsewhere = false:none

## Naming
# Style Definitions
Expand Down
10 changes: 5 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@
<PackageVersion Include="ContentFeedNuget" Version="$(ToolingPackagesVersion)" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="9.0.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
<PackageVersion Include="EssentialCSharp.Shared.Models" Version="$(ToolingPackagesVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
<PackageVersion Include="AspNet.Security.OAuth.GitHub" Version="8.3.0" />
<PackageVersion Include="Azure.Identity" Version="1.12.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="EssentialCSharp.Shared.Models" Version="$(ToolingPackagesVersion)" />
<PackageVersion Include="HtmlAgilityPack" Version="1.11.72" />
<PackageVersion Include="IntelliTect.Multitool" Version="1.5.3" />
<PackageVersion Include="Mailjet.Api" Version="3.0.0" />
Expand All @@ -31,6 +28,9 @@
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.12" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace EssentialCSharp.Web.Data;

public class EssentialCSharpWebContext(DbContextOptions<EssentialCSharpWebContext> options) : IdentityDbContext<EssentialCSharpWebUser>(options)
public class EssentialCSharpWebContext(DbContextOptions options) : IdentityDbContext<EssentialCSharpWebUser>(options)
{
protected override void OnModelCreating(ModelBuilder builder)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public class EssentialCSharpWebUser : IdentityUser
public virtual string? FirstName { get; set; }
[ProtectedPersonalData]
public virtual string? LastName { get; set; }
public int ReferralCount { get; set; }
}

Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System.ComponentModel.DataAnnotations;
using EssentialCSharp.Web.Areas.Identity.Data;
using EssentialCSharp.Web.Services.Referrals;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account;

public class LoginModel(SignInManager<EssentialCSharpWebUser> signInManager, UserManager<EssentialCSharpWebUser> userManager, ILogger<LoginModel> logger) : PageModel
public class LoginModel(SignInManager<EssentialCSharpWebUser> signInManager, UserManager<EssentialCSharpWebUser> userManager, ILogger<LoginModel> logger, IReferralService referralService) : PageModel
{
private InputModel? _Input;
[BindProperty]
Expand Down Expand Up @@ -77,6 +78,8 @@ public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
if (foundUser is not null)
{
result = await signInManager.PasswordSignInAsync(foundUser, Input.Password, Input.RememberMe, lockoutOnFailure: true);
// Call the referral service to get the referral ID and set it onto the user claim
_ = await referralService.EnsureReferralIdAsync(foundUser);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public static class ManageNavPages

public static string TwoFactorAuthentication => "TwoFactorAuthentication";

public static string Referrals => "Referrals";

public static string? IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);

public static string? EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
Expand All @@ -40,6 +42,8 @@ public static class ManageNavPages

public static string? TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);

public static string? ReferralsNavClass(ViewContext viewContext) => PageNavClass(viewContext, Referrals);

public static string? PageNavClass(ViewContext viewContext, string page)
{
string? activePage = viewContext.ViewData["ActivePage"] as string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@page
@model ReferralsDataModel
@{
ViewData["Title"] = "Referrals";
ViewData["ActivePage"] = ManageNavPages.Referrals;
}

<h3>@ViewData["Title"]</h3>

<div>
<p>
You have <strong>@Model.ReferralCount</strong> referrals.
</p>
</div>

@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using EssentialCSharp.Web.Areas.Identity.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account.Manage;

public class ReferralsDataModel : PageModel
{
private readonly UserManager<EssentialCSharpWebUser> _UserManager;
private readonly ILogger<ReferralsDataModel> _Logger;

public int ReferralCount { get; private set; }

public ReferralsDataModel(
UserManager<EssentialCSharpWebUser> userManager,
ILogger<ReferralsDataModel> logger)
{
_UserManager = userManager;
_Logger = logger;
}

public async Task<IActionResult> OnGetAsync()
{
EssentialCSharpWebUser? user = await _UserManager.GetUserAsync(User);
if (user is null)
{
return NotFound($"Unable to load user with ID '{_UserManager.GetUserId(User)}'.");
}
ReferralCount = user.ReferralCount;

return Page();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<ul class="nav nav-pills flex-column">
<li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
<li class="nav-item"><a class="account-nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
<li class="nav-item"><a class="account-nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li>
<li class="nav-item"><a class="account-nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
@if (hasExternalLogins)
{
<li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>
<li id="external-logins" class="nav-item"><a id="external-login" class="account-nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>
}
<li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
<li class="nav-item"><a class="account-nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
<li class="nav-item"><a class="account-nav-link @ManageNavPages.ReferralsNavClass(ViewContext)" id="referrals" asp-page="./Referrals">Referrals</a></li>
<li class="nav-item"><a class="account-nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
</ul>
35 changes: 12 additions & 23 deletions EssentialCSharp.Web/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,20 @@

namespace EssentialCSharp.Web.Controllers;

public class HomeController : Controller
public class HomeController(ILogger<HomeController> logger, IWebHostEnvironment hostingEnvironment, ISiteMappingService siteMappingService, IHttpContextAccessor httpContextAccessor) : Controller
{
private readonly IConfiguration _Configuration;
private readonly IWebHostEnvironment _HostingEnvironment;
private readonly ISiteMappingService _SiteMappingService;
private readonly ILogger<HomeController> _Logger;

public HomeController(ILogger<HomeController> logger, IWebHostEnvironment hostingEnvironment, ISiteMappingService siteMappingService, IConfiguration configuration)
{
_Logger = logger;
_HostingEnvironment = hostingEnvironment;
_SiteMappingService = siteMappingService;
_Configuration = configuration;
}

public IActionResult Index(string key)
{
// if no key (default case), then load up home page
SiteMapping? siteMapping = _SiteMappingService.SiteMappings.Find(key);
SiteMapping? siteMapping = siteMappingService.SiteMappings.Find(key);

if (string.IsNullOrEmpty(key))
{
return RedirectToAction(nameof(Home));
}
else if (siteMapping is not null)
{
string filePath = Path.Combine(_HostingEnvironment.ContentRootPath, Path.Combine(siteMapping.PagePath));
string filePath = Path.Combine(hostingEnvironment.ContentRootPath, Path.Combine(siteMapping.PagePath));
HtmlDocument doc = new();
doc.Load(filePath);
string headHtml = doc.DocumentNode.Element("html").Element("head").InnerHtml;
Expand All @@ -44,6 +31,8 @@ public IActionResult Index(string key)
ViewBag.PreviousPage = FlipPage(siteMapping.ChapterNumber, siteMapping.PageNumber, false);
ViewBag.HeadContents = headHtml;
ViewBag.Contents = html;
// Set the referral Id for use in the front end if available
ViewBag.ReferralId = httpContextAccessor.HttpContext?.User?.Claims?.FirstOrDefault(f => f.Type == ClaimsExtensions.ReferrerIdClaimType)?.Value;
return View();
}
else
Expand Down Expand Up @@ -84,19 +73,19 @@ public IActionResult Home()
public IActionResult Guidelines()
{
ViewBag.PageTitle = "Coding Guidelines";
FileInfo fileInfo = new(Path.Combine(_HostingEnvironment.ContentRootPath, "Guidelines", "guidelines.json"));
FileInfo fileInfo = new(Path.Combine(hostingEnvironment.ContentRootPath, "Guidelines", "guidelines.json"));
if (!fileInfo.Exists)
{
return RedirectToAction(nameof(Error), new { errorMessage = "Guidelines could not be found", statusCode = 404 });
}
ViewBag.Guidelines = fileInfo.ReadGuidelineJsonFromInputDirectory(_Logger);
ViewBag.Guidelines = fileInfo.ReadGuidelineJsonFromInputDirectory(logger);
ViewBag.GuidelinesUrl = Request.Path.Value;
return View();
}

private string FlipPage(int currentChapter, int currentPage, bool next)
{
if (_SiteMappingService.SiteMappings.Count == 0)
if (siteMappingService.SiteMappings.Count == 0)
{
return "";
}
Expand All @@ -107,18 +96,18 @@ private string FlipPage(int currentChapter, int currentPage, bool next)
page = 1;
}

SiteMapping? siteMap = _SiteMappingService.SiteMappings.FirstOrDefault(f => f.ChapterNumber == currentChapter && f.PageNumber == currentPage + page);
SiteMapping? siteMap = siteMappingService.SiteMappings.FirstOrDefault(f => f.ChapterNumber == currentChapter && f.PageNumber == currentPage + page);

if (siteMap is null)
{
if (next)
{
siteMap = _SiteMappingService.SiteMappings.FirstOrDefault(f => f.ChapterNumber == currentChapter + 1 && f.PageNumber == 1);
siteMap = siteMappingService.SiteMappings.FirstOrDefault(f => f.ChapterNumber == currentChapter + 1 && f.PageNumber == 1);
}
else
{
int? previousPage = _SiteMappingService.SiteMappings.LastOrDefault(f => f.ChapterNumber == currentChapter - 1)?.PageNumber;
siteMap = _SiteMappingService.SiteMappings.FirstOrDefault(f => f.ChapterNumber == currentChapter - 1 && f.PageNumber == previousPage);
int? previousPage = siteMappingService.SiteMappings.LastOrDefault(f => f.ChapterNumber == currentChapter - 1)?.PageNumber;
siteMap = siteMappingService.SiteMappings.FirstOrDefault(f => f.ChapterNumber == currentChapter - 1 && f.PageNumber == previousPage);
}
if (siteMap is null)
{
Expand Down
6 changes: 3 additions & 3 deletions EssentialCSharp.Web/EssentialCSharp.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="AspNet.Security.OAuth.GitHub" />
<PackageReference Include="EssentialCSharp.Shared.Models" />
<PackageReference Include="HtmlAgilityPack" />
<PackageReference Include="IntelliTect.Multitool" />
<PackageReference Include="Mailjet.Api" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" />
<PackageReference Include="Microsoft.AspNetCore.Components" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
<PackageReference Include="Octokit" />
Expand Down
17 changes: 17 additions & 0 deletions EssentialCSharp.Web/Extensions/ClaimsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Security.Claims;

namespace EssentialCSharp.Web.Extensions;

public static class ClaimsExtensions
{
public static string? GetReferrerId(this ClaimsPrincipal claimsPrincipal)
{
return claimsPrincipal.FindFirstValue(ReferrerIdClaimType);
}

public static string? GetReferrerId(this IList<Claim> claims)
{
return claims.FirstOrDefault(claim => claim.Type == ReferrerIdClaimType)?.Value;
}
public const string ReferrerIdClaimType = "ReferrerId";
}
37 changes: 37 additions & 0 deletions EssentialCSharp.Web/Middleware/ReferralTrackingMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Security.Claims;
using System.Web;
using EssentialCSharp.Web.Areas.Identity.Data;
using EssentialCSharp.Web.Services.Referrals;
using Microsoft.AspNetCore.Identity;

namespace EssentialCSharp.Web.Middleware;

public sealed class ReferralMiddleware
{
private readonly RequestDelegate _Next;

public ReferralMiddleware(RequestDelegate next)
{
_Next = next;
}

public async Task InvokeAsync(HttpContext context, IReferralService referralService, UserManager<EssentialCSharpWebUser> userManager)
{
// Retrieve current referral Id for processing
System.Collections.Specialized.NameValueCollection query = HttpUtility.ParseQueryString(context.Request.QueryString.Value!);
string? referralId = query["rid"];
if (string.IsNullOrWhiteSpace(referralId))
{
await _Next(context);
return;
}
if (context.User is { Identity.IsAuthenticated: true } claimsUser)
{
referralService.TrackReferralAsync(referralId, claimsUser);
}
else
{
referralService.TrackReferralAsync(referralId, null);
}
}
}
Loading