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