Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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,19 @@
@page
@model ReferralsDataModel
@{
ViewData["Title"] = "Referrals";
ViewData["ActivePage"] = ManageNavPages.Referrals;
}

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

<div class="row">
<div class="col-md-6">
<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; 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
18 changes: 18 additions & 0 deletions EssentialCSharp.Web/Extensions/ClaimsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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";
}
}
42 changes: 42 additions & 0 deletions EssentialCSharp.Web/Middleware/ReferralTrackingMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 (context.User is { Identity.IsAuthenticated: true } claimsUser)
{
TrackReferralAsync(referralService, referralId, claimsUser);
}
else
{
TrackReferralAsync(referralService, referralId, null);
}

await _Next(context);

static void TrackReferralAsync(IReferralService referralService, string? referralId, ClaimsPrincipal? claimsUser)
{
if (!string.IsNullOrWhiteSpace(referralId))
{
referralService.TrackReferralAsync(referralId, claimsUser);
}
}
}
}
Loading