From a4a90f9acbc5e363341fce107e51877fc1f19d1d Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Mon, 14 Sep 2026 15:52:50 +0200 Subject: [PATCH] IdentityUser extension: retarget net10.0, NUnit 4, roles, model test Retarget both projects from net7.0 to net10.0 and move the six framework packages to 10.0.12. Test stack moves to Microsoft.NET.Test.Sdk 18.10.0, NUnit 4.6.1, NUnit3TestAdapter 6.3.0, NUnit.Analyzers 4.15.0 and coverlet.collector 10.0.1; the existing assertions are constraint-model already and needed no rewrite. Clear the four CS8618 warnings: required on DisplayName, Post.Title and Post.Text, and a collection expression on Posts so the first live test can still construct a user without posts. AddDefaultIdentity registers no RoleManager and no role store, so the AspNetRoles and AspNetUserRoles tables this context creates had nothing driving them. Add .AddRoles>() in Program.cs and in TestServiceCollectionBuilder. Add Services/UserProfileService.RecordSignIn so the article's runtime example has a home in the sample. Regenerate the migration on EF Core 10. The generated Up() is identical to the 2023 file column for column; only the ProductVersion annotation moves, from 7.0.9 to 10.0.12, and the default schema version still creates seven Identity tables with no passkey table. Add ApplicationUserModelTest, which builds the model over Sqlite with no database and asserts the Guid key, the 100 character DisplayName cap and the absence of AspNetUserPasskeys. Both existing tests carry Live in the name, so CI ran zero tests on this folder; these three run under the FullyQualifiedName!~Live filter. --- .../IdentityUserExtension.csproj | 14 ++--- ...14135203_CreateIdentitySchema.Designer.cs} | 4 +- ...=> 20260914135203_CreateIdentitySchema.cs} | 0 .../ApplicationDbContextModelSnapshot.cs | 2 +- .../Models/ApplicationUser.cs | 4 +- .../IdentityUserExtension/Models/Post.cs | 4 +- .../IdentityUserExtension/Program.cs | 2 + .../Services/UserProfileService.cs | 28 +++++++++ .../Tests/ApplicationUserModelTest.cs | 58 +++++++++++++++++++ .../Tests/TestServiceCollectionBuilder.cs | 2 + .../IdentityUserExtension/Tests/Tests.csproj | 12 ++-- 11 files changed, 110 insertions(+), 20 deletions(-) rename aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/{20230719071400_CreateIdentitySchema.Designer.cs => 20260914135203_CreateIdentitySchema.Designer.cs} (99%) rename aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/{20230719071400_CreateIdentitySchema.cs => 20260914135203_CreateIdentitySchema.cs} (100%) create mode 100644 aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Services/UserProfileService.cs create mode 100644 aspnetcore-features/IdentityUserExtension/Tests/ApplicationUserModelTest.cs diff --git a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/IdentityUserExtension.csproj b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/IdentityUserExtension.csproj index 1a37e70b2c..aefee55223 100644 --- a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/IdentityUserExtension.csproj +++ b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/IdentityUserExtension.csproj @@ -1,19 +1,19 @@ - net7.0 + net10.0 enable enable aspnet-IdentityUserExtension-41039A27-11C0-4BD1-98EF-4FF82DC4AB8A - - - - - - + + + + + + diff --git a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20230719071400_CreateIdentitySchema.Designer.cs b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20260914135203_CreateIdentitySchema.Designer.cs similarity index 99% rename from aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20230719071400_CreateIdentitySchema.Designer.cs rename to aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20260914135203_CreateIdentitySchema.Designer.cs index b9b65cbca0..cd128aecda 100644 --- a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20230719071400_CreateIdentitySchema.Designer.cs +++ b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20260914135203_CreateIdentitySchema.Designer.cs @@ -12,7 +12,7 @@ namespace IdentityUserExtension.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20230719071400_CreateIdentitySchema")] + [Migration("20260914135203_CreateIdentitySchema")] partial class CreateIdentitySchema { /// @@ -20,7 +20,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("ProductVersion", "10.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20230719071400_CreateIdentitySchema.cs b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20260914135203_CreateIdentitySchema.cs similarity index 100% rename from aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20230719071400_CreateIdentitySchema.cs rename to aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/20260914135203_CreateIdentitySchema.cs diff --git a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/ApplicationDbContextModelSnapshot.cs b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/ApplicationDbContextModelSnapshot.cs index 567ad30252..474bc5f6a9 100644 --- a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("ProductVersion", "10.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Models/ApplicationUser.cs b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Models/ApplicationUser.cs index 275c8622a1..e7d0bf9895 100644 --- a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Models/ApplicationUser.cs +++ b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Models/ApplicationUser.cs @@ -4,7 +4,7 @@ namespace IdentityUserExtension.Models; public class ApplicationUser : IdentityUser { - public string DisplayName { get; set; } + public required string DisplayName { get; set; } public DateTime LastLoginDateTime { get; set; } - public List Posts { get; set; } + public List Posts { get; set; } = []; } \ No newline at end of file diff --git a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Models/Post.cs b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Models/Post.cs index 4f738372f5..76ad01f162 100644 --- a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Models/Post.cs +++ b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Models/Post.cs @@ -3,6 +3,6 @@ namespace IdentityUserExtension.Models; public class Post { public Guid Id { get; set; } - public string Title { get; set; } - public string Text { get; set; } + public required string Title { get; set; } + public required string Text { get; set; } } \ No newline at end of file diff --git a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Program.cs b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Program.cs index 38ecb79f6b..c66bb9a1c8 100644 --- a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Program.cs +++ b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Program.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using IdentityUserExtension.Data; using IdentityUserExtension.Models; @@ -11,6 +12,7 @@ builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddRoles>() .AddEntityFrameworkStores(); builder.Services.AddControllersWithViews(); diff --git a/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Services/UserProfileService.cs b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Services/UserProfileService.cs new file mode 100644 index 0000000000..163036a4e8 --- /dev/null +++ b/aspnetcore-features/IdentityUserExtension/IdentityUserExtension/Services/UserProfileService.cs @@ -0,0 +1,28 @@ +using System.Security.Claims; +using IdentityUserExtension.Models; +using Microsoft.AspNetCore.Identity; + +namespace IdentityUserExtension.Services; + +public class UserProfileService +{ + private readonly UserManager _userManager; + + public UserProfileService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task RecordSignIn(ClaimsPrincipal principal) + { + var user = await _userManager.GetUserAsync(principal); + if (user is null) + { + return IdentityResult.Failed(); + } + + user.LastLoginDateTime = DateTime.UtcNow; + + return await _userManager.UpdateAsync(user); + } +} diff --git a/aspnetcore-features/IdentityUserExtension/Tests/ApplicationUserModelTest.cs b/aspnetcore-features/IdentityUserExtension/Tests/ApplicationUserModelTest.cs new file mode 100644 index 0000000000..520f427b48 --- /dev/null +++ b/aspnetcore-features/IdentityUserExtension/Tests/ApplicationUserModelTest.cs @@ -0,0 +1,58 @@ +using IdentityUserExtension.Data; +using IdentityUserExtension.Models; +using Microsoft.EntityFrameworkCore; + +namespace Tests; + +public class ApplicationUserModelTest +{ + private static ApplicationDbContext CreateContext() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + + return new ApplicationDbContext(options); + } + + [Test] + public void GivenTheApplicationDbContext_WhenBuildingTheModel_ThenApplicationUserHasAGuidPrimaryKey() + { + using var context = CreateContext(); + + var keyProperties = context.Model + .FindEntityType(typeof(ApplicationUser))! + .FindPrimaryKey()! + .Properties; + + Assert.That(keyProperties, Has.Count.EqualTo(1)); + Assert.That(keyProperties[0].ClrType, Is.EqualTo(typeof(Guid))); + } + + [Test] + public void GivenTheApplicationDbContext_WhenBuildingTheModel_ThenDisplayNameIsCappedAtOneHundredCharacters() + { + using var context = CreateContext(); + + var displayName = context.Model + .FindEntityType(typeof(ApplicationUser))! + .FindProperty(nameof(ApplicationUser.DisplayName))!; + + Assert.That(displayName.GetMaxLength(), Is.EqualTo(100)); + Assert.That(displayName.IsNullable, Is.False); + } + + [Test] + public void GivenTheDefaultSchemaVersion_WhenBuildingTheModel_ThenThereIsNoPasskeyTable() + { + using var context = CreateContext(); + + var tables = context.Model + .GetEntityTypes() + .Select(e => e.GetTableName()) + .ToList(); + + Assert.That(tables, Does.Not.Contain("AspNetUserPasskeys")); + Assert.That(tables, Does.Contain("AspNetUsers")); + } +} diff --git a/aspnetcore-features/IdentityUserExtension/Tests/TestServiceCollectionBuilder.cs b/aspnetcore-features/IdentityUserExtension/Tests/TestServiceCollectionBuilder.cs index e73cd316ef..b96999ad10 100644 --- a/aspnetcore-features/IdentityUserExtension/Tests/TestServiceCollectionBuilder.cs +++ b/aspnetcore-features/IdentityUserExtension/Tests/TestServiceCollectionBuilder.cs @@ -1,5 +1,6 @@ using IdentityUserExtension.Data; using IdentityUserExtension.Models; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -22,6 +23,7 @@ public static IServiceProvider CreateTestServiceProvider() services.AddDbContext(options => options.UseSqlServer(connectionString)); services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddRoles>() .AddEntityFrameworkStores(); return services.BuildServiceProvider(); diff --git a/aspnetcore-features/IdentityUserExtension/Tests/Tests.csproj b/aspnetcore-features/IdentityUserExtension/Tests/Tests.csproj index 938beb3eee..c9c03b4f3e 100644 --- a/aspnetcore-features/IdentityUserExtension/Tests/Tests.csproj +++ b/aspnetcore-features/IdentityUserExtension/Tests/Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable @@ -10,11 +10,11 @@ - - - - - + + + + +