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 @@
-
-
-
-
-
+
+
+
+
+