From f921f4c8e82fcd3a20387706b30d0d2b8400926e Mon Sep 17 00:00:00 2001 From: VahidN Date: Fri, 15 Nov 2024 21:17:14 +0330 Subject: [PATCH] Upgrade to .NET 9x --- .config/dotnet-tools.json | 2 +- .editorconfig | 20 ++ .github/dependabot.yml | 11 ++ .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 8 +- .template.config/template.json | 8 +- Directory.Build.props | 186 +++++++++--------- Directory.Packages.props | 76 +++---- README.md | 2 +- global.json | 2 +- .../ASPNETCoreIdentitySample.Common.csproj | 2 +- .../GuardToolkit/ValidationExtensions.cs | 11 +- .../PersianYeKeCommandInterceptor.cs | 2 +- .../WebToolkit/ServerInfo.cs | 15 +- ...tySample.DataLayer.InMemoryDatabase.csproj | 2 +- .../InMemoryDatabaseContext.cs | 7 +- ...TCoreIdentitySample.DataLayer.MSSQL.csproj | 2 +- .../MsSqlContextFactory.cs | 2 +- .../MsSqlDbContext.cs | 6 +- .../_01-add_migrations.cmd | 2 +- .../_02-update_db.cmd | 2 +- ...CoreIdentitySample.DataLayer.SQLite.csproj | 2 +- .../SQLiteContextFactory.cs | 2 +- .../SQLiteDbContext.cs | 6 +- .../SQLiteServiceCollectionExtensions.cs | 56 +++--- .../_01-add_migrations.cmd | 2 +- .../_02-update_db.cmd | 2 +- .../ASPNETCoreIdentitySample.DataLayer.csproj | 2 +- .../Context/ApplicationDbContext.cs | 65 +++--- .../Context/AuditableEntitiesInterceptor.cs | 28 ++- .../ASPNETCoreIdentitySample.Entities.csproj | 2 +- .../Category.cs | 7 +- .../Identity/User.cs | 11 +- .../ASPNETCoreIdentitySample.IocConfig.csproj | 2 +- .../AddIdentityOptionsExtensions.cs | 25 ++- .../CustomDataProtectionExtensions.cs | 32 +-- .../ASPNETCoreIdentitySample.MsTests.csproj | 2 +- .../CoreTests.cs | 10 +- .../TestHostingEnvironment.cs | 24 +-- .../ASPNETCoreIdentitySample.Services.csproj | 2 +- .../EfCategoryService.cs | 2 +- .../EfProductService.cs | 2 +- .../ApplicationClaimsPrincipalFactory.cs | 31 +-- .../ApplicationClaimsTransformation.cs | 48 +++-- .../Identity/ApplicationRoleManager.cs | 133 ++++++------- .../Identity/ApplicationRoleStore.cs | 26 +-- .../Identity/ApplicationSignInManager.cs | 80 +++----- .../Identity/ApplicationUserManager.cs | 83 +++----- .../Identity/ApplicationUserStore.cs | 70 +++---- .../Identity/AuthMessageSender.cs | 55 +++--- .../ConfirmEmailDataProtectorTokenProvider.cs | 15 +- .../Identity/CustomNormalizer.cs | 31 ++- .../Identity/CustomPasswordValidator.cs | 29 +-- .../Identity/CustomSecurityStampValidator.cs | 29 +-- .../Identity/CustomUserValidator.cs | 15 +- .../Identity/DataProtectionKeyService.cs | 66 +++---- .../Identity/DistributedCacheTicketStore.cs | 22 +-- .../DynamicPermissionsAuthorizationHandler.cs | 27 ++- .../Identity/Logger/DbLogger.cs | 52 +++-- .../Identity/Logger/DbLoggerProvider.cs | 19 +- .../Identity/MemoryCacheTicketStore.cs | 22 +-- .../Identity/NoBrowserCacheMiddleware.cs | 12 +- .../Identity/PasswordRules.cs | 18 +- .../Identity/SecurityTrimmingService.cs | 33 ++-- .../Identity/SiteStatService.cs | 54 +++-- .../Identity/UsersPhotoService.cs | 35 ++-- ...ASPNETCoreIdentitySample.ViewModels.csproj | 2 +- .../Identity/PagedAppLogItemsViewModel.cs | 7 +- .../Identity/PagedUsersListViewModel.cs | 7 +- .../Identity/SearchUsersViewModel.cs | 30 +-- .../ASPNETCoreIdentitySample.csproj | 2 +- .../Controllers/ChangePasswordController.cs | 93 ++++----- .../ChangeUserPasswordController.cs | 98 ++++----- .../DynamicPermissionsAreaSampleController.cs | 14 +- .../DynamicRoleClaimsManagerController.cs | 54 +++-- .../Controllers/ForgotPasswordController.cs | 85 ++++---- .../Identity/Controllers/HomeController.cs | 9 +- .../Identity/Controllers/LoginController.cs | 83 ++++---- .../Controllers/RegisterController.cs | 105 +++++----- .../Controllers/RolesManagerController.cs | 122 +++++++----- .../Controllers/SystemLogController.cs | 39 ++-- .../Controllers/TwoFactorController.cs | 99 +++++----- .../Controllers/UserCardController.cs | 38 ++-- .../Controllers/UserProfileController.cs | 107 ++++------ .../Controllers/UsersManagerController.cs | 170 ++++++++-------- .../TagHelpers/SecurityTrimmingTagHelper.cs | 21 +- .../TagHelpers/VisibilityTagHelper.cs | 4 +- .../OnlineUsersViewComponent.cs | 14 +- .../TodayBirthDaysViewComponent.cs | 15 +- .../DynamicPermissionsAreaSampleController.cs | 15 +- .../DynamicPermissionsSampleController.cs | 51 ++--- .../DynamicPermissionsTestController.cs | 51 ++--- .../Controllers/ErrorController.cs | 48 +++-- .../Controllers/HomeController.cs | 22 ++- src/ASPNETCoreIdentitySample/Program.cs | 4 +- 95 files changed, 1390 insertions(+), 1680 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 290cc36..4ca7a9f 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "8.0.8", + "version": "9.0.0", "commands": [ "dotnet-ef" ] diff --git a/.editorconfig b/.editorconfig index c6d9cb6..3f3d577 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,10 +16,30 @@ dotnet_diagnostic.CA5395.severity = suggestion # CA1510: Use 'ArgumentNullException.ThrowIfNull' instead of explicitly throwing a new exception instance dotnet_diagnostic.CA1510.severity = suggestion +dotnet_diagnostic.CA1515.severity = suggestion dotnet_diagnostic.S4487.severity = suggestion dotnet_diagnostic.S1144.severity = suggestion dotnet_diagnostic.S6605.severity = suggestion +dotnet_diagnostic.IDE0005.severity = none +dotnet_diagnostic.IDE0055.severity = none +dotnet_diagnostic.IDE0160.severity = none +dotnet_diagnostic.IDE0022.severity = none +dotnet_diagnostic.IDE0021.severity = none +dotnet_diagnostic.IDE0008.severity = none +dotnet_diagnostic.IDE0061.severity = none +dotnet_diagnostic.IDE0023.severity = none +dotnet_diagnostic.IDE0051.severity = suggestion +dotnet_diagnostic.IDE0052.severity = suggestion +dotnet_diagnostic.IDE0058.severity = suggestion +dotnet_diagnostic.IDE0046.severity = suggestion +dotnet_diagnostic.IDE0048.severity = suggestion +dotnet_diagnostic.IDE0010.severity = suggestion +dotnet_diagnostic.IDE0045.severity = suggestion +dotnet_diagnostic.IDE0130.severity = suggestion +dotnet_diagnostic.IDE0072.severity = suggestion + + # CA1852: Type can be sealed because it has no subtypes in its containing assembly and is not externally visible dotnet_diagnostic.CA1852.severity = suggestion diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..81ef0c4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: +- package-ecosystem: nuget + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + groups: + tests: + patterns: ["*"] + update-types: ["minor", "patch"] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c19ea65..1220d38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,6 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v3 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Build DNTIdentity run: dotnet build ./src/ASPNETCoreIdentitySample/ASPNETCoreIdentitySample.csproj --configuration Release \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fc359d9..f3d1b55 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} config-file: ./.github/workflows/codeql/codeql-config.yml @@ -62,11 +62,11 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # If this step fails, then you should remove it and run the build manually (see below) # - name: Autobuild - # uses: github/codeql-action/autobuild@v2 + # uses: github/codeql-action/autobuild@v3 - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Build run: dotnet build --configuration Release @@ -81,6 +81,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.template.config/template.json b/.template.config/template.json index 9ee5dee..9dffe08 100644 --- a/.template.config/template.json +++ b/.template.config/template.json @@ -17,12 +17,12 @@ "datatype": "choice", "choices": [ { - "choice": "net8.0", - "description": "Target .NET 8" + "choice": "net9.0", + "description": "Target .NET 9" } ], - "defaultValue": "net8.0", - "replaces": "net8.0" + "defaultValue": "net9.0", + "replaces": "net9.0" } } } diff --git a/Directory.Build.props b/Directory.Build.props index 0cc8ffe..0ddda2a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,96 +1,106 @@ - - latest - true - true - latest - AllEnabledByDefault - true - true - true - enable - true - true - true - - $(NoWarn);CA2007;CA1056;CA1054;CA1055;MA0004;RCS1090 - $(NoError);CA2007;CA1056;CA1054;CA1055;MA0004;RCS1090 - true - strict - true - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - + $(NoWarn);CA2007;CA1056;CA1054;CA1055;MA0004;RCS1090 + $(NoError);CA2007;CA1056;CA1054;CA1055;MA0004;RCS1090 + true + strict + true + + + + true + all + low + + $(WarningsNotAsErrors);NU1900;NU1901;NU1902;NU1903;NU1904 + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 5ccf53d..237c0c6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,40 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index b6c0a72..84affdd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ 
-# سفارشی سازی ASP.NET Core Identity SDK-8.0.400 +# سفارشی سازی ASP.NET Core Identity SDK-9.0.100

diff --git a/global.json b/global.json index f6ee1ca..30094e8 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.400", + "version": "9.0.100", "rollForward": "latestMajor", "allowPrerelease": false } diff --git a/src/ASPNETCoreIdentitySample.Common/ASPNETCoreIdentitySample.Common.csproj b/src/ASPNETCoreIdentitySample.Common/ASPNETCoreIdentitySample.Common.csproj index 3817496..d579f16 100644 --- a/src/ASPNETCoreIdentitySample.Common/ASPNETCoreIdentitySample.Common.csproj +++ b/src/ASPNETCoreIdentitySample.Common/ASPNETCoreIdentitySample.Common.csproj @@ -1,6 +1,6 @@  - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ValidationExtensions.cs b/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ValidationExtensions.cs index ba63450..fd3d21d 100644 --- a/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ValidationExtensions.cs +++ b/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ValidationExtensions.cs @@ -13,19 +13,24 @@ public static string GetValidationErrors(this DbContext context) } var errors = new StringBuilder(); + var entities = context.ChangeTracker.Entries() - .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified) + .Where(e => e.State is EntityState.Added or EntityState.Modified) .Select(e => e.Entity); + foreach (var entity in entities) { var validationContext = new ValidationContext(entity); var validationResults = new List(); - if (!Validator.TryValidateObject(entity, validationContext, validationResults, true)) + + if (!Validator.TryValidateObject(entity, validationContext, validationResults, validateAllProperties: true)) { foreach (var validationResult in validationResults) { var names = validationResult.MemberNames.Aggregate((s1, s2) => $"{s1}, {s2}"); - errors.AppendFormat(CultureInfo.InvariantCulture, "{0}: {1}", names, validationResult.ErrorMessage); + + errors.AppendFormat(CultureInfo.InvariantCulture, format: "{0}: {1}", names, + validationResult.ErrorMessage); } } } diff --git a/src/ASPNETCoreIdentitySample.Common/PersianToolkit/PersianYeKeCommandInterceptor.cs b/src/ASPNETCoreIdentitySample.Common/PersianToolkit/PersianYeKeCommandInterceptor.cs index c469998..19cc922 100644 --- a/src/ASPNETCoreIdentitySample.Common/PersianToolkit/PersianYeKeCommandInterceptor.cs +++ b/src/ASPNETCoreIdentitySample.Common/PersianToolkit/PersianYeKeCommandInterceptor.cs @@ -108,7 +108,7 @@ private static void ApplyCorrectYeKe(DbCommand command) case DbType.String: case DbType.StringFixedLength: case DbType.Xml: - if (parameter.Value is not DBNull && parameter.Value is string) + if (parameter.Value is not DBNull and string) { parameter.Value = Convert.ToString(parameter.Value, CultureInfo.InvariantCulture).ApplyCorrectYeKe(); diff --git a/src/ASPNETCoreIdentitySample.Common/WebToolkit/ServerInfo.cs b/src/ASPNETCoreIdentitySample.Common/WebToolkit/ServerInfo.cs index e97704a..15d316e 100644 --- a/src/ASPNETCoreIdentitySample.Common/WebToolkit/ServerInfo.cs +++ b/src/ASPNETCoreIdentitySample.Common/WebToolkit/ServerInfo.cs @@ -2,10 +2,12 @@ public static class ServerInfo { - private static readonly string[] BinSplitArray = new[] { "bin" }; + private static readonly string[] BinSplitArray = ["bin"]; + public static string GetAppDataFolderPath() { - var appDataFolderPath = Path.Combine(GetWwwRootPath(), "App_Data"); + var appDataFolderPath = Path.Combine(GetWwwRootPath(), path2: "App_Data"); + if (!Directory.Exists(appDataFolderPath)) { Directory.CreateDirectory(appDataFolderPath); @@ -15,9 +17,6 @@ public static string GetAppDataFolderPath() } public static string GetWwwRootPath() - { - return Path.Combine( - AppContext.BaseDirectory.Split(BinSplitArray, StringSplitOptions.RemoveEmptyEntries)[0], - "wwwroot"); - } -} + => Path.Combine(AppContext.BaseDirectory.Split(BinSplitArray, StringSplitOptions.RemoveEmptyEntries)[0], + path2: "wwwroot"); +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase.csproj b/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase.csproj index b9b51be..c21a692 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase.csproj +++ b/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseContext.cs b/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseContext.cs index 3e3551d..8c49ac8 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseContext.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseContext.cs @@ -3,9 +3,4 @@ namespace ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase; -public class InMemoryDatabaseContext : ApplicationDbContext -{ - public InMemoryDatabaseContext(DbContextOptions options) : base(options) - { - } -} \ No newline at end of file +public class InMemoryDatabaseContext(DbContextOptions options) : ApplicationDbContext(options); \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/ASPNETCoreIdentitySample.DataLayer.MSSQL.csproj b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/ASPNETCoreIdentitySample.DataLayer.MSSQL.csproj index 1f6fcfe..8a787e9 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/ASPNETCoreIdentitySample.DataLayer.MSSQL.csproj +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/ASPNETCoreIdentitySample.DataLayer.MSSQL.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlContextFactory.cs b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlContextFactory.cs index cae6932..bd50fa5 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlContextFactory.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlContextFactory.cs @@ -28,7 +28,7 @@ public MsSqlDbContext CreateDbContext(string[] args) .AddJsonFile("appsettings.json", false, true) .Build(); services.AddSingleton(provider => configuration); - services.Configure(options => configuration.Bind(options)); + services.Configure(configuration.Bind); var serviceProvider = services.BuildServiceProvider(); var siteSettings = serviceProvider.GetRequiredService>(); diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlDbContext.cs b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlDbContext.cs index b88410b..cb27f9d 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlDbContext.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlDbContext.cs @@ -3,12 +3,8 @@ namespace ASPNETCoreIdentitySample.DataLayer.MSSQL; -public class MsSqlDbContext : ApplicationDbContext +public class MsSqlDbContext(DbContextOptions options) : ApplicationDbContext(options) { - public MsSqlDbContext(DbContextOptions options) : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { if (builder == null) diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_01-add_migrations.cmd b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_01-add_migrations.cmd index 94b9d84..0085afd 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_01-add_migrations.cmd +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_01-add_migrations.cmd @@ -1,6 +1,6 @@ For /f "tokens=2-4 delims=/ " %%a in ('date /t') do (set mydate=%%c_%%a_%%b) For /f "tokens=1-2 delims=/:" %%a in ("%TIME: =0%") do (set mytime=%%a%%b) -dotnet tool update --global dotnet-ef --version 8.0.2 +dotnet tool update --global dotnet-ef --version 9.0.0 dotnet tool restore dotnet build dotnet ef --verbose migrations --startup-project ../ASPNETCoreIdentitySample/ add V%mydate%_%mytime% --context MsSqlDbContext diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_02-update_db.cmd b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_02-update_db.cmd index 6557a2b..be3543c 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_02-update_db.cmd +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_02-update_db.cmd @@ -1,4 +1,4 @@ -dotnet tool update --global dotnet-ef --version 8.0.2 +dotnet tool update --global dotnet-ef --version 9.0.0 dotnet tool restore dotnet build dotnet ef --verbose --startup-project ../ASPNETCoreIdentitySample/ database update --context MsSqlDbContext diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/ASPNETCoreIdentitySample.DataLayer.SQLite.csproj b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/ASPNETCoreIdentitySample.DataLayer.SQLite.csproj index 1f0db34..edfd74e 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/ASPNETCoreIdentitySample.DataLayer.SQLite.csproj +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/ASPNETCoreIdentitySample.DataLayer.SQLite.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteContextFactory.cs b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteContextFactory.cs index 3eb6e35..dae568f 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteContextFactory.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteContextFactory.cs @@ -28,7 +28,7 @@ public SQLiteDbContext CreateDbContext(string[] args) .AddJsonFile("appsettings.json", false, true) .Build(); services.AddSingleton(_ => configuration); - services.Configure(options => configuration.Bind(options)); + services.Configure(configuration.Bind); var buildServiceProvider = services.BuildServiceProvider(); var siteSettings = buildServiceProvider.GetRequiredService>(); diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteDbContext.cs b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteDbContext.cs index c0b4c48..65729f4 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteDbContext.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteDbContext.cs @@ -4,12 +4,8 @@ namespace ASPNETCoreIdentitySample.DataLayer.SQLite; -public class SQLiteDbContext : ApplicationDbContext +public class SQLiteDbContext(DbContextOptions options) : ApplicationDbContext(options) { - public SQLiteDbContext(DbContextOptions options) : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteServiceCollectionExtensions.cs b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteServiceCollectionExtensions.cs index c28e4dd..089bcdf 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteServiceCollectionExtensions.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteServiceCollectionExtensions.cs @@ -19,22 +19,26 @@ public static IServiceCollection AddConfiguredSQLiteDbContext(this IServiceColle { var context = serviceProvider.GetRequiredService(); SetCascadeOnSaveChanges(context); + return context; }); - services.AddDbContextPool( - (serviceProvider, optionsBuilder) => optionsBuilder.UseConfiguredSQLite(siteSettings, serviceProvider)); + + services.AddDbContextPool((serviceProvider, optionsBuilder) + => optionsBuilder.UseConfiguredSQLite(siteSettings, serviceProvider)); + return services; } - private static void SetCascadeOnSaveChanges(DbContext context) + private static void SetCascadeOnSaveChanges(ApplicationDbContext context) { // To fix https://github.com/dotnet/efcore/issues/19786 context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; } - public static void UseConfiguredSQLite( - this DbContextOptionsBuilder optionsBuilder, SiteSettings siteSettings, IServiceProvider serviceProvider) + public static void UseConfiguredSQLite(this DbContextOptionsBuilder optionsBuilder, + SiteSettings siteSettings, + IServiceProvider serviceProvider) { if (optionsBuilder == null) { @@ -47,25 +51,24 @@ public static void UseConfiguredSQLite( } var connectionString = siteSettings.GetSQLiteDbConnectionString(); - optionsBuilder.UseSqlite( - connectionString, - sqlServerOptionsBuilder => - { - sqlServerOptionsBuilder.CommandTimeout((int)TimeSpan.FromMinutes(3).TotalSeconds); - sqlServerOptionsBuilder.MigrationsAssembly(typeof(SQLiteServiceCollectionExtensions).Assembly - .FullName); - }); + + optionsBuilder.UseSqlite(connectionString, sqlServerOptionsBuilder => + { + sqlServerOptionsBuilder.CommandTimeout((int)TimeSpan.FromMinutes(minutes: 3).TotalSeconds); + sqlServerOptionsBuilder.MigrationsAssembly(typeof(SQLiteServiceCollectionExtensions).Assembly.FullName); + }); + optionsBuilder.AddInterceptors(new PersianYeKeCommandInterceptor(), serviceProvider.GetRequiredService()); + optionsBuilder.ConfigureWarnings(warnings => { - warnings.Log( - (CoreEventId.LazyLoadOnDisposedContextWarning, LogLevel.Warning), + warnings.Log((CoreEventId.LazyLoadOnDisposedContextWarning, LogLevel.Warning), (CoreEventId.DetachedLazyLoadingWarning, LogLevel.Warning), (CoreEventId.ManyServiceProvidersCreatedWarning, LogLevel.Warning), - (CoreEventId.SensitiveDataLoggingEnabledWarning, LogLevel.Information) - ); + (CoreEventId.SensitiveDataLoggingEnabledWarning, LogLevel.Information)); }); + optionsBuilder.EnableSensitiveDataLogging().EnableDetailedErrors(); } @@ -76,18 +79,13 @@ public static string GetSQLiteDbConnectionString(this SiteSettings siteSettingsV throw new ArgumentNullException(nameof(siteSettingsValue)); } - switch (siteSettingsValue.ActiveDatabase) + return siteSettingsValue.ActiveDatabase switch { - case ActiveDatabase.SQLite: - return siteSettingsValue.ConnectionStrings - .SQLite - .ApplicationDbContextConnection - .ReplaceDataDirectoryInConnectionString(); - - default: - throw new NotSupportedException( - "Please set the ActiveDatabase in appsettings.json file to `SQLite`."); - } + ActiveDatabase.SQLite => siteSettingsValue.ConnectionStrings.SQLite.ApplicationDbContextConnection + .ReplaceDataDirectoryInConnectionString(), + _ => throw new NotSupportedException( + message: "Please set the ActiveDatabase in appsettings.json file to `SQLite`.") + }; } public static string ReplaceDataDirectoryInConnectionString(this string connectionString) @@ -97,7 +95,7 @@ public static string ReplaceDataDirectoryInConnectionString(this string connecti return null; } - return connectionString.Replace("|DataDirectory|", ServerInfo.GetAppDataFolderPath(), + return connectionString.Replace(oldValue: "|DataDirectory|", ServerInfo.GetAppDataFolderPath(), StringComparison.OrdinalIgnoreCase); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_01-add_migrations.cmd b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_01-add_migrations.cmd index be0d358..7820639 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_01-add_migrations.cmd +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_01-add_migrations.cmd @@ -1,6 +1,6 @@ For /f "tokens=2-4 delims=/ " %%a in ('date /t') do (set mydate=%%c_%%a_%%b) For /f "tokens=1-2 delims=/:" %%a in ("%TIME: =0%") do (set mytime=%%a%%b) -dotnet tool update --global dotnet-ef --version 8.0.2 +dotnet tool update --global dotnet-ef --version 9.0.0 dotnet tool restore dotnet build dotnet ef migrations --startup-project ../ASPNETCoreIdentitySample/ add V%mydate%_%mytime% --context SQLiteDbContext diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_02-update_db.cmd b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_02-update_db.cmd index c85cdaa..2b6d71a 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_02-update_db.cmd +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_02-update_db.cmd @@ -1,4 +1,4 @@ -dotnet tool update --global dotnet-ef --version 8.0.2 +dotnet tool update --global dotnet-ef --version 9.0.0 dotnet tool restore dotnet build dotnet ef --startup-project ../ASPNETCoreIdentitySample/ database update --context SQLiteDbContext diff --git a/src/ASPNETCoreIdentitySample.DataLayer/ASPNETCoreIdentitySample.DataLayer.csproj b/src/ASPNETCoreIdentitySample.DataLayer/ASPNETCoreIdentitySample.DataLayer.csproj index 3125d7c..4746771 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/ASPNETCoreIdentitySample.DataLayer.csproj +++ b/src/ASPNETCoreIdentitySample.DataLayer/ASPNETCoreIdentitySample.DataLayer.csproj @@ -1,6 +1,6 @@  - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Context/ApplicationDbContext.cs b/src/ASPNETCoreIdentitySample.DataLayer/Context/ApplicationDbContext.cs index df26714..0f0996c 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Context/ApplicationDbContext.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Context/ApplicationDbContext.cs @@ -13,17 +13,13 @@ namespace ASPNETCoreIdentitySample.DataLayer.Context; /// and http://www.dntips.ir/post/2578 /// plus http://www.dntips.ir/post/2491 /// -public class ApplicationDbContext : - IdentityDbContext, - IUnitOfWork +public class ApplicationDbContext(DbContextOptions options) + : IdentityDbContext(options), IUnitOfWork { // we can't use constructor injection anymore, because we are using the `AddDbContextPool<>` - public ApplicationDbContext(DbContextOptions options) - : base(options) - { - } public virtual DbSet Categories { set; get; } + public virtual DbSet Products { set; get; } protected override void OnModelCreating(ModelBuilder builder) @@ -46,56 +42,51 @@ protected override void OnModelCreating(ModelBuilder builder) #region BaseClass public virtual DbSet AppLogItems { get; set; } + public virtual DbSet AppSqlCache { get; set; } + public virtual DbSet AppDataProtectionKeys { get; set; } - public void AddRange(IEnumerable entities) where TEntity : class - { - Set().AddRange(entities); - } + public void AddRange(IEnumerable entities) + where TEntity : class + => Set().AddRange(entities); public async Task ExecuteTransactionAsync(Func action) { // https://www.dntips.ir/post/3247 var strategy = Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => - { - await using var transaction = await Database.BeginTransactionAsync(); - await action(); - await transaction.CommitAsync(); - }); + { + await using var transaction = await Database.BeginTransactionAsync(); + await action(); + await transaction.CommitAsync(); + }); } - public void ExecuteSqlInterpolatedCommand(FormattableString query) - { - Database.ExecuteSqlInterpolated(query); - } + public void ExecuteSqlInterpolatedCommand(FormattableString query) => Database.ExecuteSqlInterpolated(query); public void ExecuteSqlRawCommand(string query, params object[] parameters) - { - Database.ExecuteSqlRaw(query, parameters); - } + => Database.ExecuteSqlRaw(query, parameters); - public T GetShadowPropertyValue(object entity, string propertyName) where T : IConvertible + public T GetShadowPropertyValue(object entity, string propertyName) + where T : IConvertible { var value = Entry(entity).Property(propertyName).CurrentValue; - return value != null - ? (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture) - : default; + + return value != null ? (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture) : default; } - public object GetShadowPropertyValue(object entity, string propertyName) => - Entry(entity).Property(propertyName).CurrentValue; + public object GetShadowPropertyValue(object entity, string propertyName) + => Entry(entity).Property(propertyName).CurrentValue; - public void MarkAsChanged(TEntity entity) where TEntity : class - { - Update(entity); - } + public void MarkAsChanged(TEntity entity) + where TEntity : class + => Update(entity); - public void RemoveRange(IEnumerable entities) where TEntity : class - { - Set().RemoveRange(entities); - } + public void RemoveRange(IEnumerable entities) + where TEntity : class + => Set().RemoveRange(entities); #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Context/AuditableEntitiesInterceptor.cs b/src/ASPNETCoreIdentitySample.DataLayer/Context/AuditableEntitiesInterceptor.cs index 8d5a4ac..6acfd9b 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Context/AuditableEntitiesInterceptor.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Context/AuditableEntitiesInterceptor.cs @@ -9,22 +9,17 @@ namespace ASPNETCoreIdentitySample.DataLayer.Context; -public class AuditableEntitiesInterceptor : SaveChangesInterceptor +public class AuditableEntitiesInterceptor( + IHttpContextAccessor httpContextAccessor, + ILogger logger) : SaveChangesInterceptor { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger _logger; + private readonly IHttpContextAccessor _httpContextAccessor = + httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); - public AuditableEntitiesInterceptor( - IHttpContextAccessor httpContextAccessor, - ILogger logger) - { - _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } + private readonly ILogger _logger = + logger ?? throw new ArgumentNullException(nameof(logger)); - public override InterceptionResult SavingChanges( - DbContextEventData eventData, - InterceptionResult result) + public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result) { if (eventData?.Context is null) { @@ -32,11 +27,11 @@ public override InterceptionResult SavingChanges( } BeforeSaveTriggers(eventData.Context); + return result; } - public override ValueTask> SavingChangesAsync( - DbContextEventData eventData, + public override ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { @@ -46,6 +41,7 @@ public override ValueTask> SavingChangesAsync( } BeforeSaveTriggers(eventData.Context); + return ValueTask.FromResult(result); } @@ -58,12 +54,14 @@ private void BeforeSaveTriggers(DbContext context) private void ValidateEntities(DbContext context) { var errors = context?.GetValidationErrors(); + if (string.IsNullOrWhiteSpace(errors)) { return; } _logger.LogErrorMessage(errors); + throw new InvalidOperationException(errors); } diff --git a/src/ASPNETCoreIdentitySample.Entities/ASPNETCoreIdentitySample.Entities.csproj b/src/ASPNETCoreIdentitySample.Entities/ASPNETCoreIdentitySample.Entities.csproj index e0a4323..7f7c3eb 100644 --- a/src/ASPNETCoreIdentitySample.Entities/ASPNETCoreIdentitySample.Entities.csproj +++ b/src/ASPNETCoreIdentitySample.Entities/ASPNETCoreIdentitySample.Entities.csproj @@ -1,6 +1,6 @@  - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.Entities/Category.cs b/src/ASPNETCoreIdentitySample.Entities/Category.cs index 0fb21aa..885a97a 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Category.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Category.cs @@ -4,12 +4,9 @@ namespace ASPNETCoreIdentitySample.Entities; public class Category : IAuditableEntity { - public int Id { get; set; } + public Category() => Products = []; - public Category() - { - Products = new HashSet(); - } + public int Id { get; set; } public string Name { get; set; } diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/User.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/User.cs index 9579f09..7259944 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/User.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/User.cs @@ -12,13 +12,13 @@ public class User : IdentityUser, IAuditableEntity { public User() { - UserUsedPasswords = new HashSet(); - UserTokens = new HashSet(); + UserUsedPasswords = []; + UserTokens = []; } - [StringLength(450)] public string FirstName { get; set; } + [StringLength(maximumLength: 450)] public string FirstName { get; set; } - [StringLength(450)] public string LastName { get; set; } + [StringLength(maximumLength: 450)] public string LastName { get; set; } [NotMapped] public string DisplayName @@ -26,11 +26,12 @@ public string DisplayName get { var displayName = $"{FirstName} {LastName}"; + return string.IsNullOrWhiteSpace(displayName) ? UserName : displayName; } } - [StringLength(450)] public string PhotoFileName { get; set; } + [StringLength(maximumLength: 450)] public string PhotoFileName { get; set; } public DateTime? BirthDate { get; set; } diff --git a/src/ASPNETCoreIdentitySample.IocConfig/ASPNETCoreIdentitySample.IocConfig.csproj b/src/ASPNETCoreIdentitySample.IocConfig/ASPNETCoreIdentitySample.IocConfig.csproj index d835832..6e21d15 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/ASPNETCoreIdentitySample.IocConfig.csproj +++ b/src/ASPNETCoreIdentitySample.IocConfig/ASPNETCoreIdentitySample.IocConfig.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.IocConfig/AddIdentityOptionsExtensions.cs b/src/ASPNETCoreIdentitySample.IocConfig/AddIdentityOptionsExtensions.cs index e65ddf3..6459ad5 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/AddIdentityOptionsExtensions.cs +++ b/src/ASPNETCoreIdentitySample.IocConfig/AddIdentityOptionsExtensions.cs @@ -12,8 +12,7 @@ public static class AddIdentityOptionsExtensions { public const string EmailConfirmationTokenProviderName = "ConfirmEmail"; - public static IServiceCollection AddIdentityOptions( - this IServiceCollection services, SiteSettings siteSettings) + public static IServiceCollection AddIdentityOptions(this IServiceCollection services, SiteSettings siteSettings) { if (siteSettings == null) { @@ -21,18 +20,21 @@ public static IServiceCollection AddIdentityOptions( } services.AddConfirmEmailDataProtectorTokenOptions(siteSettings); + services.AddIdentity(identityOptions => { SetPasswordOptions(identityOptions.Password, siteSettings); SetSignInOptions(identityOptions.SignIn, siteSettings); SetUserOptions(identityOptions.User); SetLockoutOptions(identityOptions.Lockout, siteSettings); - }).AddUserStore() + }) + .AddUserStore() .AddUserManager() .AddRoleStore() .AddRoleManager() .AddSignInManager() .AddErrorDescriber() + // You **cannot** use .AddEntityFrameworkStores() when you customize everything //.AddEntityFrameworkStores() .AddDefaultTokenProviders() @@ -64,11 +66,11 @@ private static void AddConfirmEmailDataProtectorTokenOptions(this IServiceCollec } private static void EnableImmediateLogout(this IServiceCollection services) - { - services.Configure(options => + => services.Configure(options => { // enables immediate logout, after updating the user's stat. options.ValidationInterval = TimeSpan.Zero; + options.OnRefreshingPrincipal = principalContext => { // Invoked when the default security stamp validator replaces the user's ClaimsPrincipal in the cookie. @@ -80,15 +82,16 @@ private static void EnableImmediateLogout(this IServiceCollection services) return Task.CompletedTask; }; }); - } private static void SetApplicationCookieOptions(IServiceProvider provider, - CookieAuthenticationOptions identityOptionsCookies, SiteSettings siteSettings) + CookieAuthenticationOptions identityOptionsCookies, + SiteSettings siteSettings) { identityOptionsCookies.Cookie.Name = siteSettings.CookieOptions.CookieName; identityOptionsCookies.Cookie.HttpOnly = true; identityOptionsCookies.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; identityOptionsCookies.Cookie.SameSite = SameSiteMode.Lax; + identityOptionsCookies.Cookie.IsEssential = true; // this cookie will always be stored regardless of the user's consent @@ -122,12 +125,8 @@ private static void SetPasswordOptions(PasswordOptions identityOptionsPassword, } private static void SetSignInOptions(SignInOptions identityOptionsSignIn, SiteSettings siteSettings) - { - identityOptionsSignIn.RequireConfirmedEmail = siteSettings.EnableEmailConfirmation; - } + => identityOptionsSignIn.RequireConfirmedEmail = siteSettings.EnableEmailConfirmation; private static void SetUserOptions(UserOptions identityOptionsUser) - { - identityOptionsUser.RequireUniqueEmail = true; - } + => identityOptionsUser.RequireUniqueEmail = true; } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.IocConfig/CustomDataProtectionExtensions.cs b/src/ASPNETCoreIdentitySample.IocConfig/CustomDataProtectionExtensions.cs index 085ef97..3e7e071 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/CustomDataProtectionExtensions.cs +++ b/src/ASPNETCoreIdentitySample.IocConfig/CustomDataProtectionExtensions.cs @@ -13,8 +13,8 @@ namespace ASPNETCoreIdentitySample.IocConfig; public static class CustomDataProtectionExtensions { - public static IServiceCollection AddCustomDataProtection( - this IServiceCollection services, SiteSettings siteSettings) + public static IServiceCollection AddCustomDataProtection(this IServiceCollection services, + SiteSettings siteSettings) { if (siteSettings == null) { @@ -22,20 +22,21 @@ public static IServiceCollection AddCustomDataProtection( } services.AddSingleton(); + services.AddSingleton>(serviceProvider => { return new ConfigureOptions(options => { - serviceProvider.RunScopedService(xmlRepository => - options.XmlRepository = xmlRepository); + serviceProvider.RunScopedService(xmlRepository + => options.XmlRepository = xmlRepository); }); }); //var certificate = LoadCertificateFromFile(siteSettings) - services - .AddDataProtection() + services.AddDataProtection() .SetDefaultKeyLifetime(siteSettings.DataProtectionOptions.DataProtectionKeyLifetime) .SetApplicationName(siteSettings.DataProtectionOptions.ApplicationName); + //.ProtectKeysWithCertificate(certificate) return services; @@ -54,20 +55,19 @@ private static X509Certificate2 LoadCertificateFromFile(SiteSettings siteSetting using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) { store.Open(OpenFlags.ReadWrite); - using (var x509Certificate2 = - new X509Certificate2(fileName, certificate.Password, X509KeyStorageFlags.Exportable)) - { - store.Add(x509Certificate2); - } + + using var x509Certificate2 = X509CertificateLoader.LoadPkcs12FromFile(fileName, certificate.Password, + X509KeyStorageFlags.Exportable); + + store.Add(x509Certificate2); } - var cert = new X509Certificate2( - fileName, - certificate.Password, - X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet - | X509KeyStorageFlags.Exportable); + var cert = X509CertificateLoader.LoadPkcs12FromFile(fileName, certificate.Password, + X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + // TODO: If you are getting `Keyset does not exist`, run `wwwroot\App_Data\make-cert.cmd` again. WriteLine($"cert private key: {cert.GetRSAPrivateKey()}"); + return cert; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.MsTests/ASPNETCoreIdentitySample.MsTests.csproj b/src/ASPNETCoreIdentitySample.MsTests/ASPNETCoreIdentitySample.MsTests.csproj index e75eb9c..be31d7e 100644 --- a/src/ASPNETCoreIdentitySample.MsTests/ASPNETCoreIdentitySample.MsTests.csproj +++ b/src/ASPNETCoreIdentitySample.MsTests/ASPNETCoreIdentitySample.MsTests.csproj @@ -1,6 +1,6 @@  - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.MsTests/CoreTests.cs b/src/ASPNETCoreIdentitySample.MsTests/CoreTests.cs index 9b577d4..8b9a96f 100644 --- a/src/ASPNETCoreIdentitySample.MsTests/CoreTests.cs +++ b/src/ASPNETCoreIdentitySample.MsTests/CoreTests.cs @@ -28,10 +28,12 @@ public CoreTests() services.AddScoped(); var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json", false, true) + .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true) .Build(); - services.Configure(options => configuration.Bind(options)) + + services.Configure(configuration.Bind) .PostConfigure(x => { x.ActiveDatabase = ActiveDatabase.InMemoryDatabase; }); + services.AddSingleton(provider => configuration); services.AddCustomIdentityServices(configuration); @@ -49,11 +51,9 @@ public CoreTests() [TestMethod] public void TestUserAdminExists() - { - _serviceProvider.RunScopedService(context => + => _serviceProvider.RunScopedService(context => { var users = context.Set(); Assert.IsTrue(users.Any(x => x.UserName == "Admin")); }); - } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.MsTests/TestHostingEnvironment.cs b/src/ASPNETCoreIdentitySample.MsTests/TestHostingEnvironment.cs index 9627e6e..5d09797 100644 --- a/src/ASPNETCoreIdentitySample.MsTests/TestHostingEnvironment.cs +++ b/src/ASPNETCoreIdentitySample.MsTests/TestHostingEnvironment.cs @@ -5,19 +5,15 @@ namespace ASPNETCoreIdentitySample.MsTests; public class TestHostingEnvironment : IWebHostEnvironment { - public TestHostingEnvironment() - { - ApplicationName = typeof(TestHostingEnvironment).Assembly.FullName; - WebRootFileProvider = new NullFileProvider(); - ContentRootFileProvider = new NullFileProvider(); - ContentRootPath = AppContext.BaseDirectory; - WebRootPath = AppContext.BaseDirectory; - } - public string EnvironmentName { get; set; } - public string ApplicationName { get; set; } - public string WebRootPath { get; set; } - public IFileProvider WebRootFileProvider { get; set; } - public string ContentRootPath { get; set; } - public IFileProvider ContentRootFileProvider { get; set; } + + public string ApplicationName { get; set; } = typeof(TestHostingEnvironment).Assembly.FullName; + + public string WebRootPath { get; set; } = AppContext.BaseDirectory; + + public IFileProvider WebRootFileProvider { get; set; } = new NullFileProvider(); + + public string ContentRootPath { get; set; } = AppContext.BaseDirectory; + + public IFileProvider ContentRootFileProvider { get; set; } = new NullFileProvider(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/ASPNETCoreIdentitySample.Services.csproj b/src/ASPNETCoreIdentitySample.Services/ASPNETCoreIdentitySample.Services.csproj index 54c6f4c..f953cb2 100644 --- a/src/ASPNETCoreIdentitySample.Services/ASPNETCoreIdentitySample.Services.csproj +++ b/src/ASPNETCoreIdentitySample.Services/ASPNETCoreIdentitySample.Services.csproj @@ -1,6 +1,6 @@  - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.Services/EfCategoryService.cs b/src/ASPNETCoreIdentitySample.Services/EfCategoryService.cs index 648be0f..1531e99 100644 --- a/src/ASPNETCoreIdentitySample.Services/EfCategoryService.cs +++ b/src/ASPNETCoreIdentitySample.Services/EfCategoryService.cs @@ -24,6 +24,6 @@ public void AddNewCategory(Category category) public IList GetAllCategories() { - return _categories.ToList(); + return [.. _categories]; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/EfProductService.cs b/src/ASPNETCoreIdentitySample.Services/EfProductService.cs index 92f3adb..c452149 100644 --- a/src/ASPNETCoreIdentitySample.Services/EfProductService.cs +++ b/src/ASPNETCoreIdentitySample.Services/EfProductService.cs @@ -23,6 +23,6 @@ public void AddNewProduct(Product product) public IList GetAllProducts() { - return _products.Include(x => x.Category).ToList(); + return [.. _products.Include(x => x.Category)]; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsPrincipalFactory.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsPrincipalFactory.cs index 5025ed8..d38b9d8 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsPrincipalFactory.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsPrincipalFactory.cs @@ -1,5 +1,4 @@ using System.Security.Claims; -using System.Security.Principal; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using Microsoft.AspNetCore.Identity; @@ -11,25 +10,14 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// Customizing claims transformation in ASP.NET Core Identity /// More info: http://www.dntips.ir/post/2580 /// -public class ApplicationClaimsPrincipalFactory : UserClaimsPrincipalFactory +public class ApplicationClaimsPrincipalFactory( + IApplicationUserManager userManager, + IApplicationRoleManager roleManager, + IOptions optionsAccessor) : UserClaimsPrincipalFactory((UserManager)userManager, + (RoleManager)roleManager, optionsAccessor) { public static readonly string PhotoFileName = nameof(PhotoFileName); - private readonly IOptions _optionsAccessor; - private readonly IApplicationRoleManager _roleManager; - private readonly IApplicationUserManager _userManager; - - public ApplicationClaimsPrincipalFactory( - IApplicationUserManager userManager, - IApplicationRoleManager roleManager, - IOptions optionsAccessor) - : base((UserManager)userManager, (RoleManager)roleManager, optionsAccessor) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - _optionsAccessor = optionsAccessor ?? throw new ArgumentNullException(nameof(optionsAccessor)); - } - public override async Task CreateAsync(User user) { if (user == null) @@ -41,11 +29,13 @@ public override async Task CreateAsync(User user) principal = await base .CreateAsync( user); // adds all `Options.ClaimsIdentity.RoleClaimType -> Role Claims` automatically + `Options.ClaimsIdentity.UserIdClaimType -> userId` & `Options.ClaimsIdentity.UserNameClaimType -> userName` + AddCustomClaims(user, principal); + return principal; } - private static void AddCustomClaims(User user, IPrincipal principal) + private static void AddCustomClaims(User user, ClaimsPrincipal principal) { if (user == null) { @@ -57,13 +47,12 @@ private static void AddCustomClaims(User user, IPrincipal principal) throw new ArgumentNullException(nameof(principal)); } - ((ClaimsIdentity)principal.Identity)?.AddClaims(new[] - { + ((ClaimsIdentity)principal.Identity)?.AddClaims([ new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer), new Claim(ClaimTypes.GivenName, user.FirstName ?? string.Empty), new Claim(ClaimTypes.Surname, user.LastName ?? string.Empty), new Claim(PhotoFileName, user.PhotoFileName ?? string.Empty, ClaimValueTypes.String) - }); + ]); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsTransformation.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsTransformation.cs index 0f9aa16..8af7fc8 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsTransformation.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsTransformation.cs @@ -1,5 +1,4 @@ using System.Security.Claims; -using System.Security.Principal; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using DNTCommon.Web.Core; using Microsoft.AspNetCore.Authentication; @@ -14,21 +13,19 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// How to add existing db user's claims to the user's active directory claims. /// More info: http://www.dntips.ir/post/2762 /// -public class ApplicationClaimsTransformation : IClaimsTransformation +public class ApplicationClaimsTransformation( + IApplicationUserManager userManager, + IApplicationRoleManager roleManager, + ILogger logger) : IClaimsTransformation { - private readonly ILogger _logger; - private readonly IApplicationRoleManager _roleManager; - private readonly IApplicationUserManager _userManager; - - public ApplicationClaimsTransformation( - IApplicationUserManager userManager, - IApplicationRoleManager roleManager, - ILogger logger) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } + private readonly ILogger _logger = + logger ?? throw new ArgumentNullException(nameof(logger)); + + private readonly IApplicationRoleManager _roleManager = + roleManager ?? throw new ArgumentNullException(nameof(roleManager)); + + private readonly IApplicationUserManager _userManager = + userManager ?? throw new ArgumentNullException(nameof(userManager)); public async Task TransformAsync(ClaimsPrincipal principal) { @@ -37,7 +34,7 @@ public async Task TransformAsync(ClaimsPrincipal principal) throw new ArgumentNullException(nameof(principal)); } - if (!(principal.Identity is ClaimsIdentity identity) || !IsNtlm(identity)) + if (principal.Identity is not ClaimsIdentity identity || !IsNtlm(identity)) { return principal; } @@ -48,15 +45,17 @@ public async Task TransformAsync(ClaimsPrincipal principal) return principal; } - private async Task> AddExistingUserClaimsAsync(IIdentity identity) + private async Task> AddExistingUserClaimsAsync(ClaimsIdentity identity) { var claims = new List(); + var user = await _userManager.Users.Include(u => u.Claims) - .FirstOrDefaultAsync(u => u.UserName == identity.Name) - ; + .FirstOrDefaultAsync(u => u.UserName == identity.Name); + if (user == null) { _logger.LogErrorMessage($"Couldn't find {identity.Name}."); + return claims; } @@ -67,8 +66,7 @@ private async Task> AddExistingUserClaimsAsync(IIdentity iden if (_userManager.SupportsUserSecurityStamp) { - claims.Add(new Claim(options.SecurityStampClaimType, - await _userManager.GetSecurityStampAsync(user))); + claims.Add(new Claim(options.SecurityStampClaimType, await _userManager.GetSecurityStampAsync(user))); } if (_userManager.SupportsUserClaim) @@ -79,6 +77,7 @@ private async Task> AddExistingUserClaimsAsync(IIdentity iden if (_userManager.SupportsUserRole) { var roles = await _userManager.GetRolesAsync(user); + foreach (var roleName in roles) { claims.Add(new Claim(options.RoleClaimType, roleName)); @@ -91,6 +90,7 @@ private async Task> AddExistingUserClaimsAsync(IIdentity iden if (_roleManager.SupportsRoleClaims) { var role = await _roleManager.FindByNameAsync(roleName); + if (role != null) { claims.AddRange(await _roleManager.GetClaimsAsync(role)); @@ -102,8 +102,6 @@ private async Task> AddExistingUserClaimsAsync(IIdentity iden return claims; } - private static bool IsNtlm(IIdentity identity) - { - return string.Equals(identity.AuthenticationType, "NTLM", StringComparison.OrdinalIgnoreCase); - } + private static bool IsNtlm(ClaimsIdentity identity) + => string.Equals(identity.AuthenticationType, b: "NTLM", StringComparison.OrdinalIgnoreCase); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleManager.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleManager.cs index 4e8fda8..6ba2569 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleManager.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleManager.cs @@ -14,9 +14,7 @@ namespace ASPNETCoreIdentitySample.Services.Identity; ///

/// More info: http://www.dntips.ir/post/2578 /// -public class ApplicationRoleManager : - RoleManager, - IApplicationRoleManager +public class ApplicationRoleManager : RoleManager, IApplicationRoleManager { private readonly IHttpContextAccessor _contextAccessor; private readonly IdentityErrorDescriber _errors; @@ -27,16 +25,14 @@ public class ApplicationRoleManager : private readonly IUnitOfWork _uow; private readonly DbSet _users; - public ApplicationRoleManager( - IApplicationRoleStore store, + public ApplicationRoleManager(IApplicationRoleStore store, IEnumerable> roleValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, ILogger logger, IHttpContextAccessor contextAccessor, - IUnitOfWork uow) : - base((RoleStore)store, roleValidators, keyNormalizer, - errors, logger) + IUnitOfWork uow) : base((RoleStore)store, roleValidators, + keyNormalizer, errors, logger) { _store = store ?? throw new ArgumentNullException(nameof(store)); _roleValidators = roleValidators ?? throw new ArgumentNullException(nameof(roleValidators)); @@ -48,72 +44,59 @@ public ApplicationRoleManager( _users = _uow.Set(); } - #region BaseClass - - #endregion - #region CustomMethods public IList FindCurrentUserRoles() { var userId = GetCurrentUserId(); + return FindUserRoles(userId); } public IList FindUserRoles(int userId) { var userRolesQuery = from role in Roles - from user in role.Users - where user.UserId == userId - select role; + from user in role.Users + where user.UserId == userId + select role; - return userRolesQuery.OrderBy(x => x.Name).ToList(); + return [.. userRolesQuery.OrderBy(x => x.Name)]; } - public Task> GetAllCustomRolesAsync() - { - return Roles.ToListAsync(); - } + public Task> GetAllCustomRolesAsync() => Roles.ToListAsync(); public IList GetAllCustomRolesAndUsersCountList() - { - return Roles.Select(role => - new RoleAndUsersCountViewModel + => + [ + .. Roles.Select(role => new RoleAndUsersCountViewModel { Role = role, UsersCount = role.Users.Count() - }).ToList(); - } - - public async Task GetPagedApplicationUsersInRoleListAsync( - int roleId, - int pageNumber, int recordsPerPage, - string sortByField, SortOrder sortOrder, + }) + ]; + + public async Task GetPagedApplicationUsersInRoleListAsync(int roleId, + int pageNumber, + int recordsPerPage, + string sortByField, + SortOrder sortOrder, bool showAllUsers) { var skipRecords = pageNumber * recordsPerPage; var roleUserIdsQuery = from role in Roles - where role.Id == roleId - from user in role.Users - select user.UserId; - var query = _users.Include(user => user.Roles) - .Where(user => roleUserIdsQuery.Contains(user.Id)) - .AsNoTracking(); + where role.Id == roleId + from user in role.Users + select user.UserId; + + var query = _users.Include(user => user.Roles).Where(user => roleUserIdsQuery.Contains(user.Id)).AsNoTracking(); if (!showAllUsers) { query = query.Where(x => x.IsActive); } - switch (sortByField) - { - default: - query = sortOrder == SortOrder.Descending - ? query.OrderByDescending(x => x.Id) - : query.OrderBy(x => x.Id); - break; - } + query = sortOrder == SortOrder.Descending ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id); return new PagedUsersListViewModel { @@ -129,65 +112,59 @@ from user in role.Users public IList GetApplicationUsersInRole(string roleName) { var roleUserIdsQuery = from role in Roles - where role.Name == roleName - from user in role.Users - select user.UserId; - return _uow.Set().Where(applicationUser => roleUserIdsQuery.Contains(applicationUser.Id)) - .ToList(); + where role.Name == roleName + from user in role.Users + select user.UserId; + + return [.. _uow.Set().Where(applicationUser => roleUserIdsQuery.Contains(applicationUser.Id))]; } public IList GetRolesForCurrentUser() { var userId = GetCurrentUserId(); + return GetRolesForUser(userId); } public IList GetRolesForUser(int userId) { var roles = FindUserRoles(userId); - if (roles == null || !roles.Any()) - { - return new List(); - } - return roles.ToList(); + return roles == null || !roles.Any() ? [] : roles.ToList(); } public IList GetUserRolesInRole(string roleName) - { - return Roles.Where(role => role.Name == roleName) - .SelectMany(role => role.Users) - .ToList(); - } + => [.. Roles.Where(role => role.Name == roleName).SelectMany(role => role.Users)]; public bool IsCurrentUserInRole(string roleName) { var userId = GetCurrentUserId(); + return IsUserInRole(userId, roleName); } public bool IsUserInRole(int userId, string roleName) { var userRolesQuery = from role in Roles - where role.Name == roleName - from user in role.Users - where user.UserId == userId - select role; + where role.Name == roleName + from user in role.Users + where user.UserId == userId + select role; + var userRole = userRolesQuery.FirstOrDefault(); + return userRole != null; } public Task FindRoleIncludeRoleClaimsAsync(int roleId) - { - return Roles.Include(x => x.Claims).FirstOrDefaultAsync(x => x.Id == roleId); - } + => Roles.Include(x => x.Claims).FirstOrDefaultAsync(x => x.Id == roleId); - public async Task AddOrUpdateRoleClaimsAsync( - int roleId, + public async Task AddOrUpdateRoleClaimsAsync(int roleId, string roleClaimType, IList selectedRoleClaimValues) { var role = await FindRoleIncludeRoleClaimsAsync(roleId); + if (role == null) { return IdentityResult.Failed(new IdentityError @@ -197,14 +174,15 @@ public async Task AddOrUpdateRoleClaimsAsync( }); } - var currentRoleClaimValues = role.Claims.Where(roleClaim => - string.Equals(roleClaim.ClaimType, roleClaimType, StringComparison.Ordinal)) + var currentRoleClaimValues = role.Claims + .Where(roleClaim => string.Equals(roleClaim.ClaimType, roleClaimType, StringComparison.Ordinal)) .Select(roleClaim => roleClaim.ClaimValue) .ToList(); - selectedRoleClaimValues ??= new List(); + selectedRoleClaimValues ??= []; var newClaimValuesToAdd = selectedRoleClaimValues.Except(currentRoleClaimValues).ToList(); + foreach (var claimValue in newClaimValuesToAdd) { role.Claims.Add(new RoleClaim @@ -216,11 +194,13 @@ public async Task AddOrUpdateRoleClaimsAsync( } var removedClaimValues = currentRoleClaimValues.Except(selectedRoleClaimValues).ToList(); + foreach (var claimValue in removedClaimValues) { - var roleClaim = role.Claims.SingleOrDefault(rc => - string.Equals(rc.ClaimValue, claimValue, StringComparison.Ordinal) && - string.Equals(rc.ClaimType, roleClaimType, StringComparison.Ordinal)); + var roleClaim = role.Claims.SingleOrDefault(rc + => string.Equals(rc.ClaimValue, claimValue, StringComparison.Ordinal) && + string.Equals(rc.ClaimType, roleClaimType, StringComparison.Ordinal)); + if (roleClaim != null) { role.Claims.Remove(roleClaim); @@ -230,10 +210,7 @@ public async Task AddOrUpdateRoleClaimsAsync( return await UpdateAsync(role); } - private int GetCurrentUserId() - { - return _contextAccessor.HttpContext?.User.Identity?.GetUserId() ?? 0; - } + private int GetCurrentUserId() => _contextAccessor.HttpContext?.User.Identity?.GetUserId() ?? 0; #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleStore.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleStore.cs index 8141203..6faaeb5 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleStore.cs @@ -10,24 +10,10 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// /// More info: http://www.dntips.ir/post/2578 /// -public class ApplicationRoleStore : - RoleStore, - IApplicationRoleStore +public class ApplicationRoleStore(IUnitOfWork uow, IdentityErrorDescriber describer) + : RoleStore((ApplicationDbContext)uow, describer), + IApplicationRoleStore { - private readonly IdentityErrorDescriber _describer; - private readonly IUnitOfWork _uow; - - public ApplicationRoleStore( - IUnitOfWork uow, - IdentityErrorDescriber describer) - : base((ApplicationDbContext)uow, describer) - { - _uow = uow ?? throw new ArgumentNullException(nameof(uow)); - _describer = describer ?? throw new ArgumentNullException(nameof(describer)); - } - - #region BaseClass - protected override RoleClaim CreateRoleClaim(Role role, Claim claim) { if (role == null) @@ -47,10 +33,4 @@ protected override RoleClaim CreateRoleClaim(Role role, Claim claim) ClaimValue = claim.Value }; } - - #endregion - - #region CustomMethods - - #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationSignInManager.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationSignInManager.cs index 916e07d..ec60d8e 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationSignInManager.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationSignInManager.cs @@ -11,79 +11,43 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// /// More info: http://www.dntips.ir/post/2578 /// -public class ApplicationSignInManager : - SignInManager, - IApplicationSignInManager +public class ApplicationSignInManager( + IApplicationUserManager userManager, + IHttpContextAccessor contextAccessor, + IUserClaimsPrincipalFactory claimsFactory, + IOptions optionsAccessor, + ILogger logger, + IAuthenticationSchemeProvider schemes, + IUserConfirmation confirmation) : SignInManager((UserManager)userManager, contextAccessor, + claimsFactory, optionsAccessor, logger, schemes, confirmation), IApplicationSignInManager { - private readonly IUserClaimsPrincipalFactory _claimsFactory; - private readonly IUserConfirmation _confirmation; - private readonly IHttpContextAccessor _contextAccessor; - private readonly ILogger _logger; - private readonly IOptions _optionsAccessor; - private readonly IAuthenticationSchemeProvider _schemes; - private readonly IApplicationUserManager _userManager; - - public ApplicationSignInManager( - IApplicationUserManager userManager, - IHttpContextAccessor contextAccessor, - IUserClaimsPrincipalFactory claimsFactory, - IOptions optionsAccessor, - ILogger logger, - IAuthenticationSchemeProvider schemes, - IUserConfirmation confirmation) - : base((UserManager)userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, - confirmation) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); - _claimsFactory = claimsFactory ?? throw new ArgumentNullException(nameof(claimsFactory)); - _optionsAccessor = optionsAccessor ?? throw new ArgumentNullException(nameof(optionsAccessor)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _schemes = schemes ?? throw new ArgumentNullException(nameof(schemes)); - _confirmation = confirmation; - } + private readonly IHttpContextAccessor _contextAccessor = + contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); #region BaseClass - Task IApplicationSignInManager.IsLockedOut(User user) - { - return base.IsLockedOut(user); - } + Task IApplicationSignInManager.IsLockedOut(User user) => base.IsLockedOut(user); - Task IApplicationSignInManager.LockedOut(User user) - { - return base.LockedOut(user); - } + Task IApplicationSignInManager.LockedOut(User user) => base.LockedOut(user); - Task IApplicationSignInManager.PreSignInCheck(User user) - { - return base.PreSignInCheck(user); - } + Task IApplicationSignInManager.PreSignInCheck(User user) => base.PreSignInCheck(user); - Task IApplicationSignInManager.ResetLockout(User user) - { - return base.ResetLockout(user); - } + Task IApplicationSignInManager.ResetLockout(User user) => base.ResetLockout(user); - Task IApplicationSignInManager.SignInOrTwoFactorAsync(User user, bool isPersistent, - string loginProvider, bool bypassTwoFactor) - { - return base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); - } + Task IApplicationSignInManager.SignInOrTwoFactorAsync(User user, + bool isPersistent, + string loginProvider, + bool bypassTwoFactor) + => base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); #endregion #region CustomMethods - public bool IsCurrentUserSignedIn() - { - return IsSignedIn(_contextAccessor.HttpContext?.User); - } + public bool IsCurrentUserSignedIn() => IsSignedIn(_contextAccessor.HttpContext?.User); public Task ValidateCurrentUserSecurityStampAsync() - { - return ValidateSecurityStampAsync(_contextAccessor.HttpContext?.User); - } + => ValidateSecurityStampAsync(_contextAccessor.HttpContext?.User); #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserManager.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserManager.cs index 89634a5..6380ec6 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserManager.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserManager.cs @@ -16,54 +16,33 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// /// More info: http://www.dntips.ir/post/2578 /// -public class ApplicationUserManager : UserManager, IApplicationUserManager +public class ApplicationUserManager( + IApplicationUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + IServiceProvider services, + ILogger logger, + IHttpContextAccessor contextAccessor, + IUnitOfWork uow, + IUsedPasswordsService usedPasswordsService) : UserManager( + (UserStore)store, + optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger), + IApplicationUserManager { - private readonly IHttpContextAccessor _contextAccessor; - private readonly IdentityErrorDescriber _errors; - private readonly ILookupNormalizer _keyNormalizer; - private readonly ILogger _logger; - private readonly IOptions _optionsAccessor; - private readonly IPasswordHasher _passwordHasher; - private readonly IEnumerable> _passwordValidators; - private readonly DbSet _roles; - private readonly IServiceProvider _services; - private readonly IUnitOfWork _uow; - private readonly IUsedPasswordsService _usedPasswordsService; - private readonly DbSet _users; - private readonly IApplicationUserStore _userStore; - private readonly IEnumerable> _userValidators; - private User _currentUserInScope; + private readonly IHttpContextAccessor _contextAccessor = + contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); - public ApplicationUserManager(IApplicationUserStore store, - IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - ILookupNormalizer keyNormalizer, - IdentityErrorDescriber errors, - IServiceProvider services, - ILogger logger, - IHttpContextAccessor contextAccessor, - IUnitOfWork uow, - IUsedPasswordsService usedPasswordsService) : base( - (UserStore)store, - optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) - { - _userStore = store ?? throw new ArgumentNullException(nameof(store)); - _optionsAccessor = optionsAccessor ?? throw new ArgumentNullException(nameof(optionsAccessor)); - _passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); - _userValidators = userValidators ?? throw new ArgumentNullException(nameof(userValidators)); - _passwordValidators = passwordValidators ?? throw new ArgumentNullException(nameof(passwordValidators)); - _keyNormalizer = keyNormalizer ?? throw new ArgumentNullException(nameof(keyNormalizer)); - _errors = errors ?? throw new ArgumentNullException(nameof(errors)); - _services = services ?? throw new ArgumentNullException(nameof(services)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); - _uow = uow ?? throw new ArgumentNullException(nameof(uow)); - _usedPasswordsService = usedPasswordsService ?? throw new ArgumentNullException(nameof(usedPasswordsService)); - _users = uow.Set(); - _roles = uow.Set(); - } + private readonly DbSet _roles = uow.Set(); + + private readonly IUsedPasswordsService _usedPasswordsService = + usedPasswordsService ?? throw new ArgumentNullException(nameof(usedPasswordsService)); + + private readonly DbSet _users = uow.Set(); + private User _currentUserInScope; #region BaseClass @@ -313,15 +292,7 @@ public async Task GetPagedUsersListAsync(int pageNumber query = query.Where(x => x.IsActive); } - switch (sortByField) - { - default: - query = sortOrder == SortOrder.Descending - ? query.OrderByDescending(x => x.Id) - : query.OrderBy(x => x.Id); - - break; - } + query = sortOrder == SortOrder.Descending ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id); return new PagedUsersListViewModel { @@ -376,7 +347,7 @@ public async Task AddOrUpdateUserRolesAsync(int userId, var currentUserRoleIds = user.Roles.Select(x => x.RoleId).ToList(); - selectedRoleIds ??= new List(); + selectedRoleIds ??= []; var newRolesToAdd = selectedRoleIds.Except(currentUserRoleIds).ToList(); diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserStore.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserStore.cs index 50a176a..c8821af 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserStore.cs @@ -10,22 +10,10 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// /// More info: http://www.dntips.ir/post/2578 /// -public class ApplicationUserStore : - UserStore, - IApplicationUserStore +public class ApplicationUserStore(IUnitOfWork uow, IdentityErrorDescriber describer) + : UserStore( + (ApplicationDbContext)uow, describer), IApplicationUserStore { - private readonly IdentityErrorDescriber _describer; - private readonly IUnitOfWork _uow; - - public ApplicationUserStore( - IUnitOfWork uow, - IdentityErrorDescriber describer) - : base((ApplicationDbContext)uow, describer) - { - _uow = uow ?? throw new ArgumentNullException(nameof(uow)); - _describer = describer ?? throw new ArgumentNullException(nameof(describer)); - } - #region BaseClass protected override UserClaim CreateUserClaim(User user, Claim claim) @@ -35,8 +23,13 @@ protected override UserClaim CreateUserClaim(User user, Claim claim) throw new ArgumentNullException(nameof(user)); } - var userClaim = new UserClaim { UserId = user.Id }; + var userClaim = new UserClaim + { + UserId = user.Id + }; + userClaim.InitializeFromClaim(claim); + return userClaim; } @@ -96,48 +89,35 @@ protected override UserToken CreateUserToken(User user, string loginProvider, st }; } - Task IApplicationUserStore.AddUserTokenAsync(UserToken token) - { - return base.AddUserTokenAsync(token); - } + Task IApplicationUserStore.AddUserTokenAsync(UserToken token) => base.AddUserTokenAsync(token); Task IApplicationUserStore.FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken) - { - return base.FindRoleAsync(normalizedRoleName, cancellationToken); - } + => base.FindRoleAsync(normalizedRoleName, cancellationToken); - Task IApplicationUserStore.FindTokenAsync(User user, string loginProvider, string name, + Task IApplicationUserStore.FindTokenAsync(User user, + string loginProvider, + string name, CancellationToken cancellationToken) - { - return base.FindTokenAsync(user, loginProvider, name, cancellationToken); - } + => base.FindTokenAsync(user, loginProvider, name, cancellationToken); Task IApplicationUserStore.FindUserAsync(int userId, CancellationToken cancellationToken) - { - return base.FindUserAsync(userId, cancellationToken); - } + => base.FindUserAsync(userId, cancellationToken); - Task IApplicationUserStore.FindUserLoginAsync(int userId, string loginProvider, string providerKey, + Task IApplicationUserStore.FindUserLoginAsync(int userId, + string loginProvider, + string providerKey, CancellationToken cancellationToken) - { - return base.FindUserLoginAsync(userId, loginProvider, providerKey, cancellationToken); - } + => base.FindUserLoginAsync(userId, loginProvider, providerKey, cancellationToken); - Task IApplicationUserStore.FindUserLoginAsync(string loginProvider, string providerKey, + Task IApplicationUserStore.FindUserLoginAsync(string loginProvider, + string providerKey, CancellationToken cancellationToken) - { - return base.FindUserLoginAsync(loginProvider, providerKey, cancellationToken); - } + => base.FindUserLoginAsync(loginProvider, providerKey, cancellationToken); Task IApplicationUserStore.FindUserRoleAsync(int userId, int roleId, CancellationToken cancellationToken) - { - return base.FindUserRoleAsync(userId, roleId, cancellationToken); - } + => base.FindUserRoleAsync(userId, roleId, cancellationToken); - Task IApplicationUserStore.RemoveUserTokenAsync(UserToken token) - { - return base.RemoveUserTokenAsync(token); - } + Task IApplicationUserStore.RemoveUserTokenAsync(UserToken token) => base.RemoveUserTokenAsync(token); #endregion diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/AuthMessageSender.cs b/src/ASPNETCoreIdentitySample.Services/Identity/AuthMessageSender.cs index cdfb55d..b585c06 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/AuthMessageSender.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/AuthMessageSender.cs @@ -9,43 +9,36 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// More info: http://www.dntips.ir/post/2551 /// And http://www.dntips.ir/post/2564 /// -public class AuthMessageSender : IEmailSender, ISmsSender +public class AuthMessageSender(IOptionsSnapshot smtpConfig, IWebMailService webMailService) + : IEmailSender, ISmsSender { - private readonly IOptionsSnapshot _smtpConfig; - private readonly IWebMailService _webMailService; + private readonly IOptionsSnapshot _smtpConfig = + smtpConfig ?? throw new ArgumentNullException(nameof(smtpConfig)); - public AuthMessageSender( - IOptionsSnapshot smtpConfig, - IWebMailService webMailService) - { - _smtpConfig = smtpConfig ?? throw new ArgumentNullException(nameof(smtpConfig)); - _webMailService = webMailService ?? throw new ArgumentNullException(nameof(webMailService)); - } + private readonly IWebMailService _webMailService = + webMailService ?? throw new ArgumentNullException(nameof(webMailService)); public Task SendEmailAsync(string email, string subject, string viewNameOrPath, T model) - { - return _webMailService.SendEmailAsync( - _smtpConfig.Value.Smtp, - new[] { new MailAddress { ToName = "", ToAddress = email } }, - subject, - viewNameOrPath, - model - ); - } + => _webMailService.SendEmailAsync(_smtpConfig.Value.Smtp, [ + new MailAddress + { + ToName = "", + ToAddress = email + } + ], subject, viewNameOrPath, model); public Task SendEmailAsync(string email, string subject, string message) - { - return _webMailService.SendEmailAsync( - _smtpConfig.Value.Smtp, - new[] { new MailAddress { ToName = "", ToAddress = email } }, - subject, - message - ); - } + => _webMailService.SendEmailAsync(_smtpConfig.Value.Smtp, [ + new MailAddress + { + ToName = "", + ToAddress = email + } + ], subject, message); public Task SendSmsAsync(string number, string message) - { - // Plug in your SMS service here to send a text message. - return Task.FromResult(0); - } + => + + // Plug in your SMS service here to send a text message. + Task.FromResult(result: 0); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectorTokenProvider.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectorTokenProvider.cs index afaf6cc..75d5e6c 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectorTokenProvider.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectorTokenProvider.cs @@ -8,14 +8,13 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// /// How to override the default (1 day) TokenLifeSpan for the email confirmations. /// -public class ConfirmEmailDataProtectorTokenProvider : DataProtectorTokenProvider +public class ConfirmEmailDataProtectorTokenProvider( + IDataProtectionProvider dataProtectionProvider, + IOptions options, + ILogger> logger) + : DataProtectorTokenProvider(dataProtectionProvider, options, logger) where TUser : class { - public ConfirmEmailDataProtectorTokenProvider(IDataProtectionProvider dataProtectionProvider, - IOptions options, - ILogger> logger) : base(dataProtectionProvider, options, logger) - { - // NOTE: DataProtectionTokenProviderOptions.TokenLifespan is set to TimeSpan.FromDays(1) - // which is low for the `ConfirmEmail` task. - } + // NOTE: DataProtectionTokenProviderOptions.TokenLifespan is set to TimeSpan.FromDays(1) + // which is low for the `ConfirmEmail` task. } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/CustomNormalizer.cs b/src/ASPNETCoreIdentitySample.Services/Identity/CustomNormalizer.cs index 5dd13ae..2092210 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/CustomNormalizer.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/CustomNormalizer.cs @@ -19,6 +19,7 @@ public string NormalizeEmail(string email) email = email.Trim(); email = FixGmailDots(email); email = email.ToUpperInvariant(); + return email; } @@ -30,40 +31,38 @@ public string NormalizeName(string name) } name = name.Trim(); - name = name.ApplyCorrectYeKe() - .RemoveDiacritics() - .CleanUnderLines() - .RemovePunctuation(); - name = name.Trim().Replace(" ", "", StringComparison.OrdinalIgnoreCase); + name = name.ApplyCorrectYeKe().RemoveDiacritics().CleanUnderLines().RemovePunctuation(); + name = name.Trim().Replace(oldValue: " ", newValue: "", StringComparison.OrdinalIgnoreCase); name = name.ToUpperInvariant(); + return name; } private static string FixGmailDots(string email) { email = email.ToLowerInvariant().Trim(); - var emailParts = email.Split('@'); - var name = emailParts[0].Replace(".", string.Empty, StringComparison.OrdinalIgnoreCase); + var emailParts = email.Split(separator: '@'); + var name = emailParts[0].Replace(oldValue: ".", string.Empty, StringComparison.OrdinalIgnoreCase); + + var plusIndex = name.IndexOf(value: '+', StringComparison.OrdinalIgnoreCase); - var plusIndex = name.IndexOf('+', StringComparison.OrdinalIgnoreCase); if (plusIndex != -1) { - name = name.Substring(0, plusIndex); + name = name[..plusIndex]; } var emailDomain = emailParts[1]; - emailDomain = emailDomain.Replace("googlemail.com", "gmail.com", StringComparison.OrdinalIgnoreCase); - string[] domainsAllowedDots = - { - "gmail.com", - "facebook.com" - }; + emailDomain = emailDomain.Replace(oldValue: "googlemail.com", newValue: "gmail.com", + StringComparison.OrdinalIgnoreCase); + + string[] domainsAllowedDots = ["gmail.com", "facebook.com"]; var isFromDomainsAllowedDots = domainsAllowedDots.Any(domain => emailDomain.Equals(domain, StringComparison.OrdinalIgnoreCase)); + return !isFromDomainsAllowedDots ? email - : string.Format(CultureInfo.InvariantCulture, "{0}@{1}", name, emailDomain); + : string.Format(CultureInfo.InvariantCulture, format: "{0}@{1}", name, emailDomain); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/CustomPasswordValidator.cs b/src/ASPNETCoreIdentitySample.Services/Identity/CustomPasswordValidator.cs index 08f3220..1ae93a4 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/CustomPasswordValidator.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/CustomPasswordValidator.cs @@ -15,12 +15,12 @@ public class CustomPasswordValidator : PasswordValidator private readonly HashSet _passwordsBanList; private readonly IUsedPasswordsService _usedPasswordsService; - public CustomPasswordValidator( - IdentityErrorDescriber errors, // How to use CustomIdentityErrorDescriber + public CustomPasswordValidator(IdentityErrorDescriber errors, // How to use CustomIdentityErrorDescriber IOptionsSnapshot configurationRoot, IUsedPasswordsService usedPasswordsService) : base(errors) { _usedPasswordsService = usedPasswordsService ?? throw new ArgumentNullException(nameof(usedPasswordsService)); + if (configurationRoot == null) { throw new ArgumentNullException(nameof(configurationRoot)); @@ -31,7 +31,8 @@ public CustomPasswordValidator( if (_passwordsBanList.Count == 0) { - throw new InvalidOperationException("Please fill the passwords ban list in the appsettings.json file."); + throw new InvalidOperationException( + message: "Please fill the passwords ban list in the appsettings.json file."); } } @@ -46,7 +47,8 @@ public override async Task ValidateAsync(UserManager manag Code = "PasswordIsNotSet", Description = "لطفا کلمه‌ی عبور را تکمیل کنید." }); - return IdentityResult.Failed(errors.ToArray()); + + return IdentityResult.Failed([.. errors]); } if (string.IsNullOrWhiteSpace(user?.UserName)) @@ -56,12 +58,13 @@ public override async Task ValidateAsync(UserManager manag Code = "UserNameIsNotSet", Description = "لطفا نام کاربری را تکمیل کنید." }); - return IdentityResult.Failed(errors.ToArray()); + + return IdentityResult.Failed([.. errors]); } // First use the built-in validator var result = await base.ValidateAsync(manager, user, password); - errors = result.Succeeded ? new List() : result.Errors.ToList(); + errors = result.Succeeded ? [] : result.Errors.ToList(); // Extending the built-in validator if (password.Contains(user.UserName, StringComparison.OrdinalIgnoreCase)) @@ -71,7 +74,8 @@ public override async Task ValidateAsync(UserManager manag Code = "PasswordContainsUserName", Description = "کلمه‌ی عبور نمی‌تواند حاوی قسمتی از نام کاربری باشد." }); - return IdentityResult.Failed(errors.ToArray()); + + return IdentityResult.Failed([.. errors]); } if (!IsSafePasword(password)) @@ -81,7 +85,8 @@ public override async Task ValidateAsync(UserManager manag Code = "PasswordIsNotSafe", Description = "کلمه‌ی عبور وارد شده به سادگی قابل حدس زدن است." }); - return IdentityResult.Failed(errors.ToArray()); + + return IdentityResult.Failed([.. errors]); } if (await _usedPasswordsService.IsPreviouslyUsedPasswordAsync(user, password)) @@ -92,10 +97,11 @@ public override async Task ValidateAsync(UserManager manag Description = "لطفا کلمه‌ی عبور دیگری را انتخاب کنید. این کلمه‌ی عبور پیشتر توسط شما استفاده شده‌است و تکراری می‌باشد." }); - return IdentityResult.Failed(errors.ToArray()); + + return IdentityResult.Failed([.. errors]); } - return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray()); + return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed([.. errors]); } private static bool AreAllCharsEqual(string data) @@ -106,8 +112,9 @@ private static bool AreAllCharsEqual(string data) } data = data.ToLowerInvariant(); - var firstElement = data.ElementAt(0); + var firstElement = data.ElementAt(index: 0); var euqalCharsLen = data.ToCharArray().Count(x => x == firstElement); + if (euqalCharsLen == data.Length) { return true; diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/CustomSecurityStampValidator.cs b/src/ASPNETCoreIdentitySample.Services/Identity/CustomSecurityStampValidator.cs index 461cf48..70b905f 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/CustomSecurityStampValidator.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/CustomSecurityStampValidator.cs @@ -1,6 +1,5 @@ using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; @@ -11,26 +10,16 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// /// Keep track of on-line users /// -public class CustomSecurityStampValidator : SecurityStampValidator +public class CustomSecurityStampValidator( + IOptions options, + IApplicationSignInManager signInManager, + ISiteStatService siteStatService, + ILoggerFactory logger) : SecurityStampValidator(options, (SignInManager)signInManager, logger) { - private readonly IOptions _options; - private readonly IApplicationSignInManager _signInManager; - private readonly ISiteStatService _siteStatService; + private readonly ISiteStatService _siteStatService = + siteStatService ?? throw new ArgumentNullException(nameof(siteStatService)); - public CustomSecurityStampValidator( - IOptions options, - IApplicationSignInManager signInManager, - ISiteStatService siteStatService, - ILoggerFactory logger) - : base(options, (SignInManager)signInManager, logger) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); - _siteStatService = siteStatService ?? throw new ArgumentNullException(nameof(siteStatService)); - - } - - public TimeSpan UpdateLastModifiedDate { get; set; } = TimeSpan.FromMinutes(2); + public TimeSpan UpdateLastModifiedDate { get; set; } = TimeSpan.FromMinutes(minutes: 2); public override async Task ValidateAsync(CookieValidatePrincipalContext context) { @@ -46,6 +35,7 @@ private async Task UpdateUserLastVisitDateTimeAsync(CookieValidatePrincipalConte } var currentUtc = DateTimeOffset.UtcNow; + if (context.Options != null) { currentUtc = TimeProvider.GetUtcNow(); @@ -60,6 +50,7 @@ private async Task UpdateUserLastVisitDateTimeAsync(CookieValidatePrincipalConte } var timeElapsed = currentUtc.Subtract(issuedUtc.Value); + if (timeElapsed > UpdateLastModifiedDate) { await _siteStatService.UpdateUserLastVisitDateTimeAsync(context.Principal); diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/CustomUserValidator.cs b/src/ASPNETCoreIdentitySample.Services/Identity/CustomUserValidator.cs index 39a8509..c399ee7 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/CustomUserValidator.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/CustomUserValidator.cs @@ -14,10 +14,8 @@ public class CustomUserValidator : UserValidator { private readonly ISet _emailsBanList; - public CustomUserValidator( - IdentityErrorDescriber errors, // How to use CustomIdentityErrorDescriber - IOptionsSnapshot configurationRoot - ) : base(errors) + public CustomUserValidator(IdentityErrorDescriber errors, // How to use CustomIdentityErrorDescriber + IOptionsSnapshot configurationRoot) : base(errors) { if (configurationRoot == null) { @@ -28,7 +26,8 @@ IOptionsSnapshot configurationRoot if (!_emailsBanList.Any()) { - throw new InvalidOperationException("Please fill the emails ban list in the appsettings.json file."); + throw new InvalidOperationException( + message: "Please fill the emails ban list in the appsettings.json file."); } } @@ -36,18 +35,19 @@ public override async Task ValidateAsync(UserManager manag { // First use the built-in validator var result = await base.ValidateAsync(manager, user); - var errors = result.Succeeded ? new List() : result.Errors.ToList(); + var errors = result.Succeeded ? [] : result.Errors.ToList(); // Extending the built-in validator ValidateEmail(user, errors); ValidateUserName(user, errors); - return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray()); + return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed([.. errors]); } private void ValidateEmail(User user, List errors) { var userEmail = user?.Email; + if (string.IsNullOrWhiteSpace(userEmail)) { if (string.IsNullOrWhiteSpace(userEmail)) @@ -75,6 +75,7 @@ private void ValidateEmail(User user, List errors) private static void ValidateUserName(User user, List errors) { var userName = user?.UserName; + if (string.IsNullOrWhiteSpace(userName)) { errors.Add(new IdentityError diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/DataProtectionKeyService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/DataProtectionKeyService.cs index 2518808..a430040 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/DataProtectionKeyService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/DataProtectionKeyService.cs @@ -12,52 +12,49 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// /// More info: http://www.dntips.ir/post/2717/ /// -public class DataProtectionKeyService : IXmlRepository +public class DataProtectionKeyService(IServiceProvider serviceProvider, ILogger logger) + : IXmlRepository { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger = + logger ?? throw new ArgumentNullException(nameof(logger)); - public DataProtectionKeyService(IServiceProvider serviceProvider, ILogger logger) - { - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } + private readonly IServiceProvider _serviceProvider = + serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); public IReadOnlyCollection GetAllElements() - { - return _serviceProvider.RunScopedService>(context => + => _serviceProvider.RunScopedService>(context => { var dataProtectionKeys = context.Set().AsNoTracking(); - var logger = _logger; - return dataProtectionKeys.Select(key => TryParseKeyXml(key.XmlData, logger)).ToList().AsReadOnly(); + + return dataProtectionKeys.Select(key => TryParseKeyXml(key.XmlData, _logger)).ToList().AsReadOnly(); }); - } public void StoreElement(XElement element, string friendlyName) - { - // We need a separate context to call its SaveChanges several times, - // without using the current request's context and changing its internal state. - _serviceProvider.RunScopedService(context => - { - var dataProtectionKeys = context.Set(); - var entity = dataProtectionKeys.SingleOrDefault(k => k.FriendlyName == friendlyName); - if (entity != null) - { - entity.XmlData = element.ToString(); - dataProtectionKeys.Update(entity); - } - else + => + + // We need a separate context to call its SaveChanges several times, + // without using the current request's context and changing its internal state. + _serviceProvider.RunScopedService(context => { - dataProtectionKeys.Add(new AppDataProtectionKey + var dataProtectionKeys = context.Set(); + var entity = dataProtectionKeys.SingleOrDefault(k => k.FriendlyName == friendlyName); + + if (entity != null) + { + entity.XmlData = element.ToString(); + dataProtectionKeys.Update(entity); + } + else { - FriendlyName = friendlyName, - XmlData = element.ToString(SaveOptions.DisableFormatting) - }); - } + dataProtectionKeys.Add(new AppDataProtectionKey + { + FriendlyName = friendlyName, + XmlData = element.ToString(SaveOptions.DisableFormatting) + }); + } - context.SaveChanges(); - }); - } + context.SaveChanges(); + }); private static XElement TryParseKeyXml(string xml, ILogger logger) { @@ -68,6 +65,7 @@ private static XElement TryParseKeyXml(string xml, ILogger logger) catch (Exception e) { logger.LogWarningMessage($"An exception occurred while parsing the key xml '{xml}': {e}."); + return null; } } diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/DistributedCacheTicketStore.cs b/src/ASPNETCoreIdentitySample.Services/Identity/DistributedCacheTicketStore.cs index cf324f9..2432d20 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/DistributedCacheTicketStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/DistributedCacheTicketStore.cs @@ -8,21 +8,17 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// More info: http://www.dntips.ir/post/2581 /// And http://www.dntips.ir/post/2575 /// -public class DistributedCacheTicketStore : ITicketStore +public class DistributedCacheTicketStore(IDistributedCache cache) : ITicketStore { private const string KeyPrefix = "AuthSessionStore-"; - private readonly IDistributedCache _cache; + private readonly IDistributedCache _cache = cache ?? throw new ArgumentNullException(nameof(cache)); private readonly TicketSerializer _ticketSerializer = TicketSerializer.Default; - public DistributedCacheTicketStore(IDistributedCache cache) - { - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); - } - public async Task StoreAsync(AuthenticationTicket ticket) { - var key = $"{KeyPrefix}{Guid.NewGuid().ToString("N")}"; + var key = $"{KeyPrefix}{Guid.NewGuid():N}"; await RenewAsync(key, ticket); + return key; } @@ -32,11 +28,13 @@ public Task RenewAsync(string key, AuthenticationTicket ticket) { throw new ArgumentNullException(nameof(ticket)); } + // NOTE: Using `services.enableImmediateLogout();` will cause this method to be called per each request. var options = new DistributedCacheEntryOptions(); var expiresUtc = ticket.Properties.ExpiresUtc; + if (expiresUtc.HasValue) { options.SetAbsoluteExpiration(expiresUtc.Value); @@ -44,7 +42,7 @@ public Task RenewAsync(string key, AuthenticationTicket ticket) if (ticket.Properties.AllowRefresh ?? false) { - options.SetSlidingExpiration(TimeSpan.FromMinutes(30)); // TODO: configurable. + options.SetSlidingExpiration(TimeSpan.FromMinutes(minutes: 30)); // TODO: configurable. } return _cache.SetAsync(key, _ticketSerializer.Serialize(ticket), options); @@ -53,11 +51,9 @@ public Task RenewAsync(string key, AuthenticationTicket ticket) public async Task RetrieveAsync(string key) { var value = await _cache.GetAsync(key); + return value != null ? _ticketSerializer.Deserialize(value) : null; } - public Task RemoveAsync(string key) - { - return _cache.RemoveAsync(key); - } + public Task RemoveAsync(string key) => _cache.RemoveAsync(key); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionsAuthorizationHandler.cs b/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionsAuthorizationHandler.cs index 00d80e7..686f016 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionsAuthorizationHandler.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionsAuthorizationHandler.cs @@ -5,22 +5,17 @@ namespace ASPNETCoreIdentitySample.Services.Identity; -public class DynamicPermissionsAuthorizationHandler : AuthorizationHandler +public class DynamicPermissionsAuthorizationHandler( + ISecurityTrimmingService securityTrimmingService, + IHttpContextAccessor httpContextAccessor) : AuthorizationHandler { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ISecurityTrimmingService _securityTrimmingService; + private readonly IHttpContextAccessor _httpContextAccessor = + httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); - public DynamicPermissionsAuthorizationHandler( - ISecurityTrimmingService securityTrimmingService, - IHttpContextAccessor httpContextAccessor) - { - _securityTrimmingService = - securityTrimmingService ?? throw new ArgumentNullException(nameof(securityTrimmingService)); - _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); - } + private readonly ISecurityTrimmingService _securityTrimmingService = + securityTrimmingService ?? throw new ArgumentNullException(nameof(securityTrimmingService)); - protected override Task HandleRequirementAsync( - AuthorizationHandlerContext context, + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DynamicPermissionRequirement requirement) { if (context == null) @@ -30,13 +25,13 @@ protected override Task HandleRequirementAsync( var routeData = _httpContextAccessor.HttpContext?.GetRouteData(); - var areaName = routeData?.Values["area"]?.ToString(); + var areaName = routeData?.Values[key: "area"]?.ToString(); var area = string.IsNullOrWhiteSpace(areaName) ? string.Empty : areaName; - var controllerName = routeData?.Values["controller"]?.ToString(); + var controllerName = routeData?.Values[key: "controller"]?.ToString(); var controller = string.IsNullOrWhiteSpace(controllerName) ? string.Empty : controllerName; - var actionName = routeData?.Values["action"]?.ToString(); + var actionName = routeData?.Values[key: "action"]?.ToString(); var action = string.IsNullOrWhiteSpace(actionName) ? string.Empty : actionName; // This is just a sample: How to access form values from an AuthorizationHandler diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLogger.cs b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLogger.cs index 1cadd40..0a254c3 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLogger.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLogger.cs @@ -10,13 +10,18 @@ namespace ASPNETCoreIdentitySample.Services.Identity.Logger; public class DbLogger : ILogger { + private static readonly JsonSerializerOptions JsonSerializerOptions = new() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = true + }; + private readonly string _loggerName; private readonly DbLoggerProvider _loggerProvider; private readonly LogLevel _minLevel; private readonly IServiceProvider _serviceProvider; - public DbLogger( - DbLoggerProvider loggerProvider, + public DbLogger(DbLoggerProvider loggerProvider, IServiceProvider serviceProvider, string loggerName, IOptions siteSettings) @@ -32,8 +37,7 @@ public DbLogger( public bool IsEnabled(LogLevel logLevel) => logLevel >= _minLevel; - public void Log( - LogLevel logLevel, + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, @@ -62,25 +66,28 @@ public void Log( } var httpContextAccessor = _serviceProvider.GetService(); + var appLogItem = new AppLogItem - { - Url = httpContextAccessor?.HttpContext != null - ? httpContextAccessor.HttpContext.Request.Path.ToString() - : string.Empty, - EventId = eventId.Id, - LogLevel = logLevel.ToString(), - Logger = _loggerName, - Message = message, - }; + { + Url = httpContextAccessor?.HttpContext != null + ? httpContextAccessor.HttpContext.Request.Path.ToString() + : string.Empty, + EventId = eventId.Id, + LogLevel = logLevel.ToString(), + Logger = _loggerName, + Message = message + }; + var props = httpContextAccessor?.GetShadowProperties(); SetStateJson(state, appLogItem); - _loggerProvider.AddLogItem(new LoggerItem { Props = props, AppLogItem = appLogItem }); + + _loggerProvider.AddLogItem(new LoggerItem + { + Props = props, + AppLogItem = appLogItem + }); } - private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - WriteIndented = true, - }; + private static void SetStateJson(TState state, AppLogItem appLogItem) { try @@ -97,13 +104,16 @@ private sealed class NoopDisposable : IDisposable { public void Dispose() { - Dispose(true); + Dispose(disposing: true); GC.SuppressFinalize(this); } private static void Dispose(bool disposing) { - // empty on purpose + if (disposing) + { + // empty on purpose + } } } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerProvider.cs b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerProvider.cs index b17ff4d..08721ac 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerProvider.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerProvider.cs @@ -12,8 +12,8 @@ namespace ASPNETCoreIdentitySample.Services.Identity.Logger; public class DbLoggerProvider : ILoggerProvider { private readonly CancellationTokenSource _cancellationTokenSource = new(); - private readonly List _currentBatch = new List(); - private readonly TimeSpan _interval = TimeSpan.FromSeconds(2); + private readonly List _currentBatch = []; + private readonly TimeSpan _interval = TimeSpan.FromSeconds(seconds: 2); private readonly BlockingCollection _messageQueue = new(new ConcurrentQueue()); @@ -22,9 +22,7 @@ public class DbLoggerProvider : ILoggerProvider private readonly IOptions _siteSettings; private bool _isDisposed; - public DbLoggerProvider( - IOptions siteSettings, - IServiceProvider serviceProvider) + public DbLoggerProvider(IOptions siteSettings, IServiceProvider serviceProvider) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); _siteSettings = siteSettings ?? throw new ArgumentNullException(nameof(siteSettings)); @@ -32,13 +30,11 @@ public DbLoggerProvider( } public ILogger CreateLogger(string categoryName) - { - return new DbLogger(this, _serviceProvider, categoryName, _siteSettings); - } + => new DbLogger(this, _serviceProvider, categoryName, _siteSettings); public void Dispose() { - Dispose(true); + Dispose(disposing: true); GC.SuppressFinalize(this); } @@ -62,7 +58,7 @@ protected virtual void Dispose(bool disposing) } } - internal void AddLogItem(LoggerItem appLogItem) + public void AddLogItem(LoggerItem appLogItem) { if (!_messageQueue.IsAddingCompleted) { @@ -121,7 +117,8 @@ await _serviceProvider.RunScopedServiceAsync(async context => } } - [SuppressMessage("Microsoft.Usage", "CA1031:catch a more specific allowed exception type, or rethrow the exception", + [SuppressMessage(category: "Microsoft.Usage", + checkId: "CA1031:catch a more specific allowed exception type, or rethrow the exception", Justification = "don't throw exceptions from logger")] private void Stop() { diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/MemoryCacheTicketStore.cs b/src/ASPNETCoreIdentitySample.Services/Identity/MemoryCacheTicketStore.cs index 852a6b3..2ad648b 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/MemoryCacheTicketStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/MemoryCacheTicketStore.cs @@ -10,20 +10,16 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// More info: http://www.dntips.ir/post/2581 /// And http://www.dntips.ir/post/2575 /// -public class MemoryCacheTicketStore : ITicketStore +public class MemoryCacheTicketStore(IMemoryCache cache) : ITicketStore { private const string KeyPrefix = "AuthSessionStore-"; - private readonly IMemoryCache _cache; - - public MemoryCacheTicketStore(IMemoryCache cache) - { - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); - } + private readonly IMemoryCache _cache = cache ?? throw new ArgumentNullException(nameof(cache)); public async Task StoreAsync(AuthenticationTicket ticket) { - var key = $"{KeyPrefix}{Guid.NewGuid().ToString("N")}"; + var key = $"{KeyPrefix}{Guid.NewGuid():N}"; await RenewAsync(key, ticket); + return key; } @@ -34,7 +30,7 @@ public Task RenewAsync(string key, AuthenticationTicket ticket) throw new ArgumentNullException(nameof(ticket)); } - var options = new MemoryCacheEntryOptions().SetSize(1); + var options = new MemoryCacheEntryOptions().SetSize(size: 1); var expiresUtc = ticket.Properties.ExpiresUtc; if (expiresUtc.HasValue) @@ -44,23 +40,25 @@ public Task RenewAsync(string key, AuthenticationTicket ticket) if (ticket.Properties.AllowRefresh ?? false) { - options.SetSlidingExpiration(TimeSpan.FromMinutes(60)); //TODO: configurable. + options.SetSlidingExpiration(TimeSpan.FromMinutes(minutes: 60)); //TODO: configurable. } _cache.Set(key, ticket, options); - return Task.FromResult(0); + return Task.FromResult(result: 0); } public Task RetrieveAsync(string key) { _cache.TryGetValue(key, out AuthenticationTicket ticket); + return Task.FromResult(ticket); } public Task RemoveAsync(string key) { _cache.Remove(key); - return Task.FromResult(0); + + return Task.FromResult(result: 0); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddleware.cs b/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddleware.cs index f468d01..cb2bd37 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddleware.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddleware.cs @@ -3,18 +3,12 @@ namespace ASPNETCoreIdentitySample.Services.Identity; -public class NoBrowserCacheMiddleware +public class NoBrowserCacheMiddleware(RequestDelegate next) { - private readonly RequestDelegate _next; - - public NoBrowserCacheMiddleware(RequestDelegate next) - { - _next = next; - } - public Task Invoke(HttpContext context) { context.DisableBrowserCache(); - return _next(context); + + return next(context); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/PasswordRules.cs b/src/ASPNETCoreIdentitySample.Services/Identity/PasswordRules.cs index d561f18..96d6d17 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/PasswordRules.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/PasswordRules.cs @@ -7,39 +7,35 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// /// More info: http://www.dntips.ir/post/3407 /// -public class PasswordRules : IPasswordRules +public class PasswordRules(IOptions optionsAccessor) : IPasswordRules { - private readonly IdentityOptions _options; - - public PasswordRules( - IOptions optionsAccessor) => - _options = optionsAccessor?.Value; + private readonly IdentityOptions _options = optionsAccessor?.Value; public string GetPasswordRules() { var options = _options.Password; var passwordRules = new StringBuilder(); - passwordRules.AppendFormat(CultureInfo.InvariantCulture, "minlength: {0};", options.RequiredLength); + passwordRules.AppendFormat(CultureInfo.InvariantCulture, format: "minlength: {0};", options.RequiredLength); if (options.RequireLowercase) { - passwordRules.Append(" required: lower;"); + passwordRules.Append(value: " required: lower;"); } if (options.RequireUppercase) { - passwordRules.Append(" required: upper;"); + passwordRules.Append(value: " required: upper;"); } if (options.RequireDigit) { - passwordRules.Append(" required: digit;"); + passwordRules.Append(value: " required: digit;"); } if (options.RequireNonAlphanumeric) { - passwordRules.Append(" required: special;"); + passwordRules.Append(value: " required: special;"); } return passwordRules.ToString(); diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/SecurityTrimmingService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/SecurityTrimmingService.cs index 0371e17..199cc70 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/SecurityTrimmingService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/SecurityTrimmingService.cs @@ -8,31 +8,28 @@ namespace ASPNETCoreIdentitySample.Services.Identity; /// /// More info: http://www.dntips.ir/post/2581 /// -public class SecurityTrimmingService : ISecurityTrimmingService +public class SecurityTrimmingService( + IHttpContextAccessor httpContextAccessor, + IMvcActionsDiscoveryService mvcActionsDiscoveryService) : ISecurityTrimmingService { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IMvcActionsDiscoveryService _mvcActionsDiscoveryService; + private readonly IHttpContextAccessor _httpContextAccessor = + httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); - public SecurityTrimmingService( - IHttpContextAccessor httpContextAccessor, - IMvcActionsDiscoveryService mvcActionsDiscoveryService) - { - _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); - _mvcActionsDiscoveryService = mvcActionsDiscoveryService ?? - throw new ArgumentNullException(nameof(mvcActionsDiscoveryService)); - } + private readonly IMvcActionsDiscoveryService _mvcActionsDiscoveryService = mvcActionsDiscoveryService ?? + throw new ArgumentNullException( + nameof(mvcActionsDiscoveryService)); public bool CanCurrentUserAccess(string area, string controller, string action) - { - return _httpContextAccessor.HttpContext != null && - CanUserAccess(_httpContextAccessor.HttpContext.User, area, controller, action); - } + => _httpContextAccessor.HttpContext != null && + CanUserAccess(_httpContextAccessor.HttpContext.User, area, controller, action); public bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action) { var currentClaimValue = $"{area}:{controller}:{action}"; + var securedControllerActions = _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission); + if (securedControllerActions.SelectMany(x => x.MvcActions) .All(x => !string.Equals(x.ActionId, currentClaimValue, StringComparison.Ordinal))) { @@ -53,8 +50,8 @@ public bool CanUserAccess(ClaimsPrincipal user, string area, string controller, // Check for dynamic permissions // A user gets its permissions claims from the `ApplicationClaimsPrincipalFactory` class automatically and it includes the role claims too. - return user.HasClaim(claim => - string.Equals(claim.Type, ConstantPolicies.DynamicPermissionClaimType, StringComparison.Ordinal) && - string.Equals(claim.Value, currentClaimValue, StringComparison.Ordinal)); + return user.HasClaim(claim + => string.Equals(claim.Type, ConstantPolicies.DynamicPermissionClaimType, StringComparison.Ordinal) && + string.Equals(claim.Value, currentClaimValue, StringComparison.Ordinal)); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/SiteStatService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/SiteStatService.cs index 81309aa..91ea728 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/SiteStatService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/SiteStatService.cs @@ -8,31 +8,24 @@ namespace ASPNETCoreIdentitySample.Services.Identity; -public class SiteStatService : ISiteStatService +public class SiteStatService(IApplicationUserManager userManager, IUnitOfWork uow) : ISiteStatService { - private readonly IUnitOfWork _uow; - private readonly IApplicationUserManager _userManager; - private readonly DbSet _users; + private readonly IApplicationUserManager _userManager = + userManager ?? throw new ArgumentNullException(nameof(userManager)); - public SiteStatService( - IApplicationUserManager userManager, - IUnitOfWork uow) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _uow = uow ?? throw new ArgumentNullException(nameof(uow)); - _users = uow.Set(); - } + private readonly DbSet _users = uow.Set(); public Task> GetOnlineUsersListAsync(int numbersToTake, int minutesToTake) { var now = DateTime.UtcNow; var minutes = now.AddMinutes(-minutesToTake); + return _users.AsNoTracking() - .Where(user => user.LastVisitDateTime != null && user.LastVisitDateTime.Value <= now - && user.LastVisitDateTime.Value >= minutes) - .OrderByDescending(user => user.LastVisitDateTime) - .Take(numbersToTake) - .ToListAsync(); + .Where(user => user.LastVisitDateTime != null && user.LastVisitDateTime.Value <= now && + user.LastVisitDateTime.Value >= minutes) + .OrderByDescending(user => user.LastVisitDateTime) + .Take(numbersToTake) + .ToListAsync(); } public Task> GetTodayBirthdayListAsync() @@ -40,21 +33,22 @@ public Task> GetTodayBirthdayListAsync() var now = DateTime.UtcNow; var day = now.Day; var month = now.Month; + return _users.AsNoTracking() - .Where(user => user.BirthDate != null && user.IsActive - && user.BirthDate.Value.Day == day - && user.BirthDate.Value.Month == month) - .ToListAsync(); + .Where(user => user.BirthDate != null && user.IsActive && user.BirthDate.Value.Day == day && + user.BirthDate.Value.Month == month) + .ToListAsync(); } public async Task GetUsersAverageAge() { var users = await _users.AsNoTracking() - .Where(x => x.BirthDate != null && x.IsActive) - .OrderBy(x => x.BirthDate) - .ToListAsync(); + .Where(x => x.BirthDate != null && x.IsActive) + .OrderBy(x => x.BirthDate) + .ToListAsync(); var count = users.Count; + if (count == 0) { return new AgeStatViewModel(); @@ -63,12 +57,12 @@ public async Task GetUsersAverageAge() var sum = users.Where(user => user.BirthDate != null).Sum(user => (int?)user.BirthDate.Value.GetAge()) ?? 0; return new AgeStatViewModel - { - AverageAge = sum / count, - MaxAgeUser = users[0], - MinAgeUser = users[^1], - UsersCount = count, - }; + { + AverageAge = sum / count, + MaxAgeUser = users[index: 0], + MinAgeUser = users[^1], + UsersCount = count + }; } public async Task UpdateUserLastVisitDateTimeAsync(ClaimsPrincipal claimsPrincipal) diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/UsersPhotoService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/UsersPhotoService.cs index 27a02c6..6e11e27 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/UsersPhotoService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/UsersPhotoService.cs @@ -8,26 +8,25 @@ namespace ASPNETCoreIdentitySample.Services.Identity; -public class UsersPhotoService : IUsersPhotoService +public class UsersPhotoService( + IHttpContextAccessor contextAccessor, + IWebHostEnvironment hostingEnvironment, + IOptionsSnapshot siteSettings) : IUsersPhotoService { - private readonly IHttpContextAccessor _contextAccessor; - private readonly IWebHostEnvironment _hostingEnvironment; - private readonly IOptionsSnapshot _siteSettings; - - public UsersPhotoService( - IHttpContextAccessor contextAccessor, - IWebHostEnvironment hostingEnvironment, - IOptionsSnapshot siteSettings) - { - _contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); - _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); - _siteSettings = siteSettings ?? throw new ArgumentNullException(nameof(siteSettings)); - } + private readonly IHttpContextAccessor _contextAccessor = + contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); + + private readonly IWebHostEnvironment _hostingEnvironment = + hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + + private readonly IOptionsSnapshot _siteSettings = + siteSettings ?? throw new ArgumentNullException(nameof(siteSettings)); public string GetUsersAvatarsFolderPath() { var usersAvatarsFolder = _siteSettings.Value.UsersAvatarsFolder; var uploadsRootFolder = Path.Combine(_hostingEnvironment.WebRootPath, usersAvatarsFolder); + if (!Directory.Exists(uploadsRootFolder)) { Directory.CreateDirectory(uploadsRootFolder); @@ -44,6 +43,7 @@ public void SetUserDefaultPhoto(User user) } var avatarPath = Path.Combine(GetUsersAvatarsFolderPath(), user.PhotoFileName ?? string.Empty); + if (!File.Exists(avatarPath)) { user.PhotoFileName = _siteSettings.Value.UserDefaultPhoto; @@ -58,20 +58,23 @@ public string GetUserDefaultPhoto(string photoFileName) } var avatarPath = Path.Combine(GetUsersAvatarsFolderPath(), photoFileName); + return !File.Exists(avatarPath) ? _siteSettings.Value.UserDefaultPhoto : photoFileName; } public string GetUserPhotoUrl(string photoFileName) { photoFileName = GetUserDefaultPhoto(photoFileName); + return $"~/{_siteSettings.Value.UsersAvatarsFolder}/{photoFileName}"; } public string GetCurrentUserPhotoUrl() { var photoFileName = - _contextAccessor.HttpContext?.User.Identity?.GetUserClaimValue( - ApplicationClaimsPrincipalFactory.PhotoFileName); + _contextAccessor.HttpContext?.User.Identity?.GetUserClaimValue(ApplicationClaimsPrincipalFactory + .PhotoFileName); + return GetUserPhotoUrl(photoFileName); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/ASPNETCoreIdentitySample.ViewModels.csproj b/src/ASPNETCoreIdentitySample.ViewModels/ASPNETCoreIdentitySample.ViewModels.csproj index 75a4e10..3638734 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/ASPNETCoreIdentitySample.ViewModels.csproj +++ b/src/ASPNETCoreIdentitySample.ViewModels/ASPNETCoreIdentitySample.ViewModels.csproj @@ -1,6 +1,6 @@  - net8.0 + net9.0 diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedAppLogItemsViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedAppLogItemsViewModel.cs index cf4adbe..78dda09 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedAppLogItemsViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedAppLogItemsViewModel.cs @@ -5,14 +5,9 @@ namespace ASPNETCoreIdentitySample.ViewModels.Identity; public class PagedAppLogItemsViewModel { - public PagedAppLogItemsViewModel() - { - Paging = new PaginationSettings(); - } - public string LogLevel { get; set; } = string.Empty; public List AppLogItems { get; set; } - public PaginationSettings Paging { get; set; } + public PaginationSettings Paging { get; set; } = new(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedUsersListViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedUsersListViewModel.cs index be637f6..559e7b9 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedUsersListViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedUsersListViewModel.cs @@ -5,14 +5,9 @@ namespace ASPNETCoreIdentitySample.ViewModels.Identity; public class PagedUsersListViewModel { - public PagedUsersListViewModel() - { - Paging = new PaginationSettings(); - } - public List Users { get; set; } public List Roles { get; set; } - public PaginationSettings Paging { get; set; } + public PaginationSettings Paging { get; set; } = new(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/SearchUsersViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/SearchUsersViewModel.cs index 4f1486c..0602908 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/SearchUsersViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/SearchUsersViewModel.cs @@ -2,17 +2,13 @@ public class SearchUsersViewModel { - [Display(Name = "جستجوی عبارت")] - public string TextToFind { set; get; } + [Display(Name = "جستجوی عبارت")] public string TextToFind { set; get; } - [Display(Name = "قسمتی از ایمیل")] - public bool IsPartOfEmail { set; get; } + [Display(Name = "قسمتی از ایمیل")] public bool IsPartOfEmail { set; get; } - [Display(Name = "شماره کاربری")] - public bool IsUserId { set; get; } + [Display(Name = "شماره کاربری")] public bool IsUserId { set; get; } - [Display(Name = "قسمتی از نام")] - public bool IsPartOfName { set; get; } + [Display(Name = "قسمتی از نام")] public bool IsPartOfName { set; get; } [Display(Name = "قسمتی از نام خانوادگی")] public bool IsPartOfLastName { set; get; } @@ -20,17 +16,15 @@ public class SearchUsersViewModel [Display(Name = "قسمتی از نام کاربری")] public bool IsPartOfUserName { set; get; } - [Display(Name = "قسمتی از محل اقامت")] - public bool IsPartOfLocation { set; get; } + [Display(Name = "قسمتی از محل اقامت")] public bool IsPartOfLocation { set; get; } [Display(Name = "دارای ایمیل تائید شده")] public bool HasEmailConfirmed { set; get; } - [Display(Name = "فقط فعال‌ها")] - public bool UserIsActive { set; get; } + [Display(Name = "فقط فعال‌ها")] public bool UserIsActive { set; get; } [Display(Name = "کاربران فعال و غیرفعال")] - public bool ShowAllUsers { set; get; } + public bool ShowAllUsers { set; get; } = true; [Display(Name = "دارای حساب کاربری قفل شده")] public bool UserIsLockedOut { set; get; } @@ -40,14 +34,8 @@ public class SearchUsersViewModel [Display(Name = "تعداد ردیف بازگشتی")] [Required(ErrorMessage = "(*)")] - [Range(1, 1000, ErrorMessage = "عدد وارد شده باید در بازه 1 تا 1000 تعیین شود")] - public int MaxNumberOfRows { set; get; } + [Range(minimum: 1, maximum: 1000, ErrorMessage = "عدد وارد شده باید در بازه 1 تا 1000 تعیین شود")] + public int MaxNumberOfRows { set; get; } = 7; public PagedUsersListViewModel PagedUsersList { set; get; } - - public SearchUsersViewModel() - { - ShowAllUsers = true; - MaxNumberOfRows = 7; - } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/ASPNETCoreIdentitySample.csproj b/src/ASPNETCoreIdentitySample/ASPNETCoreIdentitySample.csproj index 9b012e9..6576a8d 100644 --- a/src/ASPNETCoreIdentitySample/ASPNETCoreIdentitySample.csproj +++ b/src/ASPNETCoreIdentitySample/ASPNETCoreIdentitySample.csproj @@ -1,6 +1,6 @@  - net8.0 + net9.0 Exe diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangePasswordController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangePasswordController.cs index dc62ec5..b7579a2 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangePasswordController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangePasswordController.cs @@ -17,40 +17,24 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; [Authorize] [Area(AreaConstants.IdentityArea)] [BreadCrumb(Title = "تغییر کلمه‌ی عبور", UseDefaultRouteUrl = true, Order = 0)] -public class ChangePasswordController : Controller +public class ChangePasswordController( + IApplicationUserManager userManager, + IApplicationSignInManager signInManager, + IEmailSender emailSender, + IPasswordValidator passwordValidator, + IUsedPasswordsService usedPasswordsService, + IOptionsSnapshot siteOptions) : Controller { - private readonly IEmailSender _emailSender; - private readonly IPasswordValidator _passwordValidator; - private readonly IApplicationSignInManager _signInManager; - private readonly IOptionsSnapshot _siteOptions; - private readonly IUsedPasswordsService _usedPasswordsService; - private readonly IApplicationUserManager _userManager; - - public ChangePasswordController( - IApplicationUserManager userManager, - IApplicationSignInManager signInManager, - IEmailSender emailSender, - IPasswordValidator passwordValidator, - IUsedPasswordsService usedPasswordsService, - IOptionsSnapshot siteOptions) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); - _passwordValidator = passwordValidator ?? throw new ArgumentNullException(nameof(passwordValidator)); - _usedPasswordsService = usedPasswordsService ?? throw new ArgumentNullException(nameof(usedPasswordsService)); - _emailSender = emailSender ?? throw new ArgumentNullException(nameof(emailSender)); - _siteOptions = siteOptions ?? throw new ArgumentNullException(nameof(siteOptions)); - } - [BreadCrumb(Title = "ایندکس", Order = 1)] public async Task Index() { var userId = User.Identity?.GetUserId() ?? 0; - var passwordChangeDate = await _usedPasswordsService.GetLastUserPasswordChangeDateAsync(userId); + var passwordChangeDate = await usedPasswordsService.GetLastUserPasswordChangeDateAsync(userId); + return View(new ChangePasswordViewModel - { - LastUserPasswordChangeDate = passwordChangeDate, - }); + { + LastUserPasswordChangeDate = passwordChangeDate + }); } [HttpPost] @@ -59,7 +43,7 @@ public async Task Index(ChangePasswordViewModel model) { if (model is null) { - return View("Error"); + return View(viewName: "Error"); } if (!ModelState.IsValid) @@ -67,35 +51,38 @@ public async Task Index(ChangePasswordViewModel model) return View(model); } - var user = await _userManager.GetCurrentUserAsync(); + var user = await userManager.GetCurrentUserAsync(); + if (user == null) { - return View("NotFound"); + return View(viewName: "NotFound"); } - var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); + var result = await userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); + if (result.Succeeded) { - await _userManager.UpdateSecurityStampAsync(user); + await userManager.UpdateSecurityStampAsync(user); // reflect the changes in the Identity cookie - await _signInManager.RefreshSignInAsync(user); - - await _emailSender.SendEmailAsync( - user.Email, - "اطلاع رسانی تغییر کلمه‌ی عبور", - "~/Areas/Identity/Views/EmailTemplates/_ChangePasswordNotification.cshtml", - new ChangePasswordNotificationViewModel - { - User = user, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString(), - }); - - return RedirectToAction(nameof(Index), "UserCard", new { id = user.Id }); + await signInManager.RefreshSignInAsync(user); + + await emailSender.SendEmailAsync(user.Email, subject: "اطلاع رسانی تغییر کلمه‌ی عبور", + viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_ChangePasswordNotification.cshtml", + new ChangePasswordNotificationViewModel + { + User = user, + EmailSignature = siteOptions.Value.Smtp.FromName, + MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() + }); + + return RedirectToAction(nameof(Index), controllerName: "UserCard", new + { + id = user.Id + }); } - ModelState.AddModelError("", result.DumpErrors(true)); + ModelState.AddModelError(key: "", result.DumpErrors(useHtmlNewLine: true)); return View(model); } @@ -109,11 +96,9 @@ await _emailSender.SendEmailAsync( [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ValidatePassword(string newPassword) { - var user = await _userManager.GetCurrentUserAsync(); - var result = await _passwordValidator.ValidateAsync( - (UserManager)_userManager, - user, - newPassword); - return Json(result.Succeeded ? "true" : result.DumpErrors(true)); + var user = await userManager.GetCurrentUserAsync(); + var result = await passwordValidator.ValidateAsync((UserManager)userManager, user, newPassword); + + return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangeUserPasswordController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangeUserPasswordController.cs index 91e9c9f..710638a 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangeUserPasswordController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangeUserPasswordController.cs @@ -18,50 +18,33 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; [Authorize(Roles = ConstantRoles.Admin)] [Area(AreaConstants.IdentityArea)] [BreadCrumb(Title = "تغییر کلمه‌ی عبور كاربر توسط مدير سيستم", UseDefaultRouteUrl = true, Order = 0)] -public class ChangeUserPasswordController : Controller +public class ChangeUserPasswordController( + IApplicationUserManager userManager, + IApplicationSignInManager signInManager, + IEmailSender emailSender, + IPasswordValidator passwordValidator, + IOptionsSnapshot siteOptions) : Controller { - private readonly IEmailSender _emailSender; - private readonly IPasswordValidator _passwordValidator; - private readonly IApplicationSignInManager _signInManager; - private readonly IOptionsSnapshot _siteOptions; - private readonly IUsedPasswordsService _usedPasswordsService; - private readonly IApplicationUserManager _userManager; - - public ChangeUserPasswordController( - IApplicationUserManager userManager, - IApplicationSignInManager signInManager, - IEmailSender emailSender, - IPasswordValidator passwordValidator, - IUsedPasswordsService usedPasswordsService, - IOptionsSnapshot siteOptions) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); - _passwordValidator = passwordValidator ?? throw new ArgumentNullException(nameof(passwordValidator)); - _usedPasswordsService = usedPasswordsService ?? throw new ArgumentNullException(nameof(usedPasswordsService)); - _emailSender = emailSender ?? throw new ArgumentNullException(nameof(emailSender)); - _siteOptions = siteOptions ?? throw new ArgumentNullException(nameof(siteOptions)); - } - [BreadCrumb(Title = "ایندکس", Order = 1)] public async Task Index(int? id) { if (!id.HasValue) { - return View("NotFound"); + return View(viewName: "NotFound"); } - var user = await _userManager.FindByIdAsync(id.Value.ToString(CultureInfo.InvariantCulture)); + var user = await userManager.FindByIdAsync(id.Value.ToString(CultureInfo.InvariantCulture)); + if (user == null) { - return View("NotFound"); + return View(viewName: "NotFound"); } return View(new ChangeUserPasswordViewModel - { - UserId = user.Id, - Name = user.UserName, - }); + { + UserId = user.Id, + Name = user.UserName + }); } [HttpPost] @@ -70,7 +53,7 @@ public async Task Index(ChangeUserPasswordViewModel model) { if (model is null) { - return View("Error"); + return View(viewName: "Error"); } if (!ModelState.IsValid) @@ -78,35 +61,38 @@ public async Task Index(ChangeUserPasswordViewModel model) return View(model); } - var user = await _userManager.FindByIdAsync(model.UserId.ToString(CultureInfo.InvariantCulture)); + var user = await userManager.FindByIdAsync(model.UserId.ToString(CultureInfo.InvariantCulture)); + if (user == null) { - return View("NotFound"); + return View(viewName: "NotFound"); } - var result = await _userManager.UpdatePasswordHash(user, model.NewPassword, true); + var result = await userManager.UpdatePasswordHash(user, model.NewPassword, validatePassword: true); + if (result.Succeeded) { - await _userManager.UpdateSecurityStampAsync(user); + await userManager.UpdateSecurityStampAsync(user); // reflect the changes in the Identity cookie - await _signInManager.RefreshSignInAsync(user); - - await _emailSender.SendEmailAsync( - user.Email, - "اطلاع رسانی تغییر کلمه‌ی عبور", - "~/Areas/Identity/Views/EmailTemplates/_ChangePasswordNotification.cshtml", - new ChangePasswordNotificationViewModel - { - User = user, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString(), - }); - - return RedirectToAction(nameof(Index), "UserCard", new { id = user.Id }); + await signInManager.RefreshSignInAsync(user); + + await emailSender.SendEmailAsync(user.Email, subject: "اطلاع رسانی تغییر کلمه‌ی عبور", + viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_ChangePasswordNotification.cshtml", + new ChangePasswordNotificationViewModel + { + User = user, + EmailSignature = siteOptions.Value.Smtp.FromName, + MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() + }); + + return RedirectToAction(nameof(Index), controllerName: "UserCard", new + { + id = user.Id + }); } - ModelState.AddModelError("", result.DumpErrors(true)); + ModelState.AddModelError(key: "", result.DumpErrors(useHtmlNewLine: true)); return View(model); } @@ -120,11 +106,9 @@ await _emailSender.SendEmailAsync( [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ValidatePassword(string newPassword, int userId) { - var user = await _userManager.FindByIdAsync(userId.ToString(CultureInfo.InvariantCulture)); - var result = await _passwordValidator.ValidateAsync( - (UserManager)_userManager, - user, - newPassword); - return Json(result.Succeeded ? "true" : result.DumpErrors(true)); + var user = await userManager.FindByIdAsync(userId.ToString(CultureInfo.InvariantCulture)); + var result = await passwordValidator.ValidateAsync((UserManager)userManager, user, newPassword); + + return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicPermissionsAreaSampleController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicPermissionsAreaSampleController.cs index 277dae5..1cbb4ac 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicPermissionsAreaSampleController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicPermissionsAreaSampleController.cs @@ -9,13 +9,13 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; /// /// More info: http://www.dntips.ir/post/2581 /// -[Authorize(Policy = ConstantPolicies.DynamicPermission), Area(AreaConstants.IdentityArea), - BreadCrumb(UseDefaultRouteUrl = true, Order = 0), DisplayName("کنترلر نمونه با سطح دسترسی پویا در یک ناحیه خاص")] +[Authorize(Policy = ConstantPolicies.DynamicPermission)] +[Area(AreaConstants.IdentityArea)] +[BreadCrumb(UseDefaultRouteUrl = true, Order = 0)] +[DisplayName(displayName: "کنترلر نمونه با سطح دسترسی پویا در یک ناحیه خاص")] public class DynamicPermissionsAreaSampleController : Controller { - [DisplayName("ایندکس"), BreadCrumb(Order = 1)] - public IActionResult Index() - { - return View(); - } + [DisplayName(displayName: "ایندکس")] + [BreadCrumb(Order = 1)] + public IActionResult Index() => View(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicRoleClaimsManagerController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicRoleClaimsManagerController.cs index ab5f220..1951145 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicRoleClaimsManagerController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicRoleClaimsManagerController.cs @@ -12,45 +12,38 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; /// /// More info: http://www.dntips.ir/post/2581 /// -[Authorize(Roles = ConstantRoles.Admin), Area(AreaConstants.IdentityArea), - BreadCrumb(Title = "مدیریت نقش‌های پویا", UseDefaultRouteUrl = true, Order = 0)] -public class DynamicRoleClaimsManagerController : Controller +[Authorize(Roles = ConstantRoles.Admin)] +[Area(AreaConstants.IdentityArea)] +[BreadCrumb(Title = "مدیریت نقش‌های پویا", UseDefaultRouteUrl = true, Order = 0)] +public class DynamicRoleClaimsManagerController( + IMvcActionsDiscoveryService mvcActionsDiscoveryService, + IApplicationRoleManager roleManager) : Controller { - private readonly IMvcActionsDiscoveryService _mvcActionsDiscoveryService; - private readonly IApplicationRoleManager _roleManager; - - public DynamicRoleClaimsManagerController( - IMvcActionsDiscoveryService mvcActionsDiscoveryService, - IApplicationRoleManager roleManager) - { - _mvcActionsDiscoveryService = mvcActionsDiscoveryService ?? - throw new ArgumentNullException(nameof(mvcActionsDiscoveryService)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - } - [BreadCrumb(Title = "ایندکس", Order = 1)] public async Task Index(int? id) { this.AddBreadCrumb(new BreadCrumb { Title = "مدیریت نقش‌ها", - Url = Url.Action("Index", "RolesManager"), + Url = Url.Action(action: "Index", controller: "RolesManager"), Order = -1 }); if (!id.HasValue) { - return View("Error"); + return View(viewName: "Error"); } - var role = await _roleManager.FindRoleIncludeRoleClaimsAsync(id.Value); + var role = await roleManager.FindRoleIncludeRoleClaimsAsync(id.Value); + if (role == null) { - return View("NotFound"); + return View(viewName: "NotFound"); } var securedControllerActions = - _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission); + mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission); + return View(new DynamicRoleClaimsManagerViewModel { SecuredControllerActions = securedControllerActions, @@ -58,23 +51,28 @@ public async Task Index(int? id) }); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task Index(DynamicRoleClaimsManagerViewModel model) { if (model is null) { - return View("Error"); + return View(viewName: "Error"); } - var result = await _roleManager.AddOrUpdateRoleClaimsAsync( - model.RoleId, - ConstantPolicies.DynamicPermissionClaimType, - model.ActionIds); + var result = await roleManager.AddOrUpdateRoleClaimsAsync(model.RoleId, + ConstantPolicies.DynamicPermissionClaimType, model.ActionIds); + if (!result.Succeeded) { - return BadRequest(result.DumpErrors(true)); + return BadRequest(result.DumpErrors(useHtmlNewLine: true)); } - return Json(new { success = true }); + return Json(new + { + success = true + }); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ForgotPasswordController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ForgotPasswordController.cs index 15857bd..c73857a 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ForgotPasswordController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ForgotPasswordController.cs @@ -18,25 +18,12 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; [Area(AreaConstants.IdentityArea)] [AllowAnonymous] [BreadCrumb(Title = "بازیابی کلمه‌ی عبور", UseDefaultRouteUrl = true, Order = 0)] -public class ForgotPasswordController : Controller +public class ForgotPasswordController( + IApplicationUserManager userManager, + IPasswordValidator passwordValidator, + IEmailSender emailSender, + IOptionsSnapshot siteOptions) : Controller { - private readonly IEmailSender _emailSender; - private readonly IPasswordValidator _passwordValidator; - private readonly IOptionsSnapshot _siteOptions; - private readonly IApplicationUserManager _userManager; - - public ForgotPasswordController( - IApplicationUserManager userManager, - IPasswordValidator passwordValidator, - IEmailSender emailSender, - IOptionsSnapshot siteOptions) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _passwordValidator = passwordValidator ?? throw new ArgumentNullException(nameof(passwordValidator)); - _emailSender = emailSender ?? throw new ArgumentNullException(nameof(emailSender)); - _siteOptions = siteOptions ?? throw new ArgumentNullException(nameof(siteOptions)); - } - [BreadCrumb(Title = "تائید کلمه‌ی عبور فراموش شده", Order = 1)] public IActionResult ForgotPasswordConfirmation() => View(); @@ -50,32 +37,31 @@ public async Task Index(ForgotPasswordViewModel model) { if (model is null) { - return View("Error"); + return View(viewName: "Error"); } if (ModelState.IsValid) { - var user = await _userManager.FindByEmailAsync(model.Email); - if (user == null || !await _userManager.IsEmailConfirmedAsync(user)) + var user = await userManager.FindByEmailAsync(model.Email); + + if (user == null || !await userManager.IsEmailConfirmedAsync(user)) { - return View("Error"); + return View(viewName: "Error"); } - var code = await _userManager.GeneratePasswordResetTokenAsync(user); - await _emailSender.SendEmailAsync( - model.Email, - "بازیابی کلمه‌ی عبور", - "~/Areas/Identity/Views/EmailTemplates/_PasswordReset.cshtml", - new PasswordResetViewModel - { - UserId = user.Id, - Token = code, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString(), - }) - ; - - return View("ForgotPasswordConfirmation"); + var code = await userManager.GeneratePasswordResetTokenAsync(user); + + await emailSender.SendEmailAsync(model.Email, subject: "بازیابی کلمه‌ی عبور", + viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_PasswordReset.cshtml", + new PasswordResetViewModel + { + UserId = user.Id, + Token = code, + EmailSignature = siteOptions.Value.Smtp.FromName, + MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() + }); + + return View(viewName: "ForgotPasswordConfirmation"); } return View(model); @@ -90,21 +76,20 @@ await _emailSender.SendEmailAsync( [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ValidatePassword(string password, string email) { - var user = await _userManager.FindByEmailAsync(email); + var user = await userManager.FindByEmailAsync(email); + if (user == null) { - return Json("ایمیل وارد شده معتبر نیست."); + return Json(data: "ایمیل وارد شده معتبر نیست."); } - var result = await _passwordValidator.ValidateAsync( - (UserManager)_userManager, - user, - password); - return Json(result.Succeeded ? "true" : result.DumpErrors(true)); + var result = await passwordValidator.ValidateAsync((UserManager)userManager, user, password); + + return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); } [BreadCrumb(Title = "تغییر کلمه‌ی عبور", Order = 1)] - public IActionResult ResetPassword(string code = null) => code == null ? View("Error") : View(); + public IActionResult ResetPassword(string code = null) => code == null ? View(viewName: "Error") : View(); [HttpPost] [ValidateAntiForgeryToken] @@ -112,7 +97,7 @@ public async Task ResetPassword(ResetPasswordViewModel model) { if (model is null) { - return View("Error"); + return View(viewName: "Error"); } if (!ModelState.IsValid) @@ -120,20 +105,22 @@ public async Task ResetPassword(ResetPasswordViewModel model) return View(model); } - var user = await _userManager.FindByEmailAsync(model.Email); + var user = await userManager.FindByEmailAsync(model.Email); + if (user == null) { // Don't reveal that the user does not exist return RedirectToAction(nameof(ResetPasswordConfirmation)); } - var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password); + var result = await userManager.ResetPasswordAsync(user, model.Code, model.Password); + if (result.Succeeded) { return RedirectToAction(nameof(ResetPasswordConfirmation)); } - ModelState.AddModelError("", result.DumpErrors(true)); + ModelState.AddModelError(key: "", result.DumpErrors(useHtmlNewLine: true)); return View(); } diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/HomeController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/HomeController.cs index ff672b2..243fd95 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/HomeController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/HomeController.cs @@ -4,12 +4,11 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; -[Area(AreaConstants.IdentityArea), Authorize, BreadCrumb(Title = "خانه", UseDefaultRouteUrl = true, Order = 0)] +[Area(AreaConstants.IdentityArea)] +[Authorize] +[BreadCrumb(Title = "خانه", UseDefaultRouteUrl = true, Order = 0)] public class HomeController : Controller { [BreadCrumb(Title = "ایندکس", Order = 1)] - public IActionResult Index() - { - return View(); - } + public IActionResult Index() => View(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/LoginController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/LoginController.cs index e2acec3..1601437 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/LoginController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/LoginController.cs @@ -13,27 +13,17 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; [Area(AreaConstants.IdentityArea)] [AllowAnonymous] [BreadCrumb(Title = "ورود به سیستم", UseDefaultRouteUrl = true, Order = 0)] -public class LoginController : Controller +public class LoginController( + IApplicationSignInManager signInManager, + IApplicationUserManager userManager, + IOptionsSnapshot siteOptions) : Controller { - private readonly IApplicationSignInManager _signInManager; - private readonly IOptionsSnapshot _siteOptions; - private readonly IApplicationUserManager _userManager; - - public LoginController( - IApplicationSignInManager signInManager, - IApplicationUserManager userManager, - IOptionsSnapshot siteOptions) - { - _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _siteOptions = siteOptions ?? throw new ArgumentNullException(nameof(siteOptions)); - } - [BreadCrumb(Title = "ایندکس", Order = 1)] [NoBrowserCache] public IActionResult Index(string returnUrl = null) { - ViewData["ReturnUrl"] = returnUrl; + ViewData[index: "ReturnUrl"] = returnUrl; + return View(); } @@ -44,37 +34,41 @@ public async Task Index(LoginViewModel model, string returnUrl = { if (model is null) { - return View("Error"); + return View(viewName: "Error"); } - ViewData["ReturnUrl"] = returnUrl; + ViewData[index: "ReturnUrl"] = returnUrl; + if (ModelState.IsValid) { - var user = await _userManager.FindByNameAsync(model.Username); + var user = await userManager.FindByNameAsync(model.Username); + if (user == null) { - ModelState.AddModelError(string.Empty, "نام کاربری و یا کلمه‌ی عبور وارد شده معتبر نیستند."); + ModelState.AddModelError(string.Empty, + errorMessage: "نام کاربری و یا کلمه‌ی عبور وارد شده معتبر نیستند."); + return View(model); } if (!user.IsActive) { - ModelState.AddModelError(string.Empty, "اکانت شما غیرفعال شده‌است."); + ModelState.AddModelError(string.Empty, errorMessage: "اکانت شما غیرفعال شده‌است."); + return View(model); } - if (_siteOptions.Value.EnableEmailConfirmation && - !await _userManager.IsEmailConfirmedAsync(user)) + if (siteOptions.Value.EnableEmailConfirmation && !await userManager.IsEmailConfirmedAsync(user)) { - ModelState.AddModelError("", "لطفا به پست الکترونیک خود مراجعه کرده و ایمیل خود را تائید کنید!"); + ModelState.AddModelError(key: "", + errorMessage: "لطفا به پست الکترونیک خود مراجعه کرده و ایمیل خود را تائید کنید!"); + return View(model); } - var result = await _signInManager.PasswordSignInAsync( - model.Username, - model.Password, - model.RememberMe, - true); + var result = await signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, + lockoutOnFailure: true); + if (result.Succeeded) { if (Url.IsLocalUrl(returnUrl)) @@ -82,29 +76,32 @@ public async Task Index(LoginViewModel model, string returnUrl = return Redirect(returnUrl); } - return RedirectToAction(nameof(HomeController.Index), "Home"); + return RedirectToAction(nameof(HomeController.Index), controllerName: "Home"); } if (result.RequiresTwoFactor) { - return RedirectToAction( - nameof(TwoFactorController.SendCode), - "TwoFactor", - new { ReturnUrl = returnUrl, model.RememberMe }); + return RedirectToAction(nameof(TwoFactorController.SendCode), controllerName: "TwoFactor", new + { + ReturnUrl = returnUrl, + model.RememberMe + }); } if (result.IsLockedOut) { - return View("~/Areas/Identity/Views/TwoFactor/Lockout.cshtml"); + return View(viewName: "~/Areas/Identity/Views/TwoFactor/Lockout.cshtml"); } if (result.IsNotAllowed) { - ModelState.AddModelError(string.Empty, "عدم دسترسی ورود."); + ModelState.AddModelError(string.Empty, errorMessage: "عدم دسترسی ورود."); + return View(model); } - ModelState.AddModelError(string.Empty, "نام کاربری و یا کلمه‌ی عبور وارد شده معتبر نیستند."); + ModelState.AddModelError(string.Empty, errorMessage: "نام کاربری و یا کلمه‌ی عبور وارد شده معتبر نیستند."); + return View(model); } @@ -115,14 +112,16 @@ public async Task Index(LoginViewModel model, string returnUrl = public async Task LogOff() { var user = User.Identity is { IsAuthenticated: true } - ? await _userManager.FindByNameAsync(User.Identity.Name) - : null; - await _signInManager.SignOutAsync(); + ? await userManager.FindByNameAsync(User.Identity.Name) + : null; + + await signInManager.SignOutAsync(); + if (user != null) { - await _userManager.UpdateSecurityStampAsync(user); + await userManager.UpdateSecurityStampAsync(user); } - return RedirectToAction(nameof(HomeController.Index), "Home"); + return RedirectToAction(nameof(HomeController.Index), controllerName: "Home"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RegisterController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RegisterController.cs index 7f93485..052c68d 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RegisterController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RegisterController.cs @@ -18,28 +18,13 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; [Area(AreaConstants.IdentityArea)] [AllowAnonymous] [BreadCrumb(Title = "ثبت نام", UseDefaultRouteUrl = true, Order = 0)] -public class RegisterController : Controller +public class RegisterController( + IApplicationUserManager userManager, + IPasswordValidator passwordValidator, + IUserValidator userValidator, + IEmailSender emailSender, + IOptionsSnapshot siteOptions) : Controller { - private readonly IEmailSender _emailSender; - private readonly IPasswordValidator _passwordValidator; - private readonly IOptionsSnapshot _siteOptions; - private readonly IApplicationUserManager _userManager; - private readonly IUserValidator _userValidator; - - public RegisterController( - IApplicationUserManager userManager, - IPasswordValidator passwordValidator, - IUserValidator userValidator, - IEmailSender emailSender, - IOptionsSnapshot siteOptions) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _passwordValidator = passwordValidator ?? throw new ArgumentNullException(nameof(passwordValidator)); - _userValidator = userValidator ?? throw new ArgumentNullException(nameof(userValidator)); - _emailSender = emailSender ?? throw new ArgumentNullException(nameof(emailSender)); - _siteOptions = siteOptions ?? throw new ArgumentNullException(nameof(siteOptions)); - } - /// /// For [Remote] validation /// @@ -49,10 +34,13 @@ public RegisterController( [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ValidateUsername(string username, string email) { - var result = await _userValidator.ValidateAsync( - (UserManager)_userManager, - new User { UserName = username, Email = email }); - return Json(result.Succeeded ? "true" : result.DumpErrors(true)); + var result = await userValidator.ValidateAsync((UserManager)userManager, new User + { + UserName = username, + Email = email + }); + + return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); } /// @@ -64,11 +52,12 @@ public async Task ValidateUsername(string username, string email) [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ValidatePassword(string password, string username) { - var result = await _passwordValidator.ValidateAsync( - (UserManager)_userManager, - new User { UserName = username }, - password); - return Json(result.Succeeded ? "true" : result.DumpErrors(true)); + var result = await passwordValidator.ValidateAsync((UserManager)userManager, new User + { + UserName = username + }, password); + + return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); } [BreadCrumb(Title = "تائید ایمیل", Order = 1)] @@ -76,16 +65,18 @@ public async Task ConfirmEmail(string userId, string code) { if (userId == null || code == null) { - return View("Error"); + return View(viewName: "Error"); } - var user = await _userManager.FindByIdAsync(userId); + var user = await userManager.FindByIdAsync(userId); + if (user == null) { - return View("NotFound"); + return View(viewName: "NotFound"); } - var result = await _userManager.ConfirmEmailAsync(user, code); + var result = await userManager.ConfirmEmailAsync(user, code); + return View(result.Succeeded ? nameof(ConfirmEmail) : "Error"); } @@ -102,38 +93,38 @@ public async Task Index(RegisterViewModel model) { if (model is null) { - return View("Error"); + return View(viewName: "Error"); } if (ModelState.IsValid) { var user = new User - { - UserName = model.Username, - Email = model.Email, - FirstName = model.FirstName, - LastName = model.LastName, - }; - var result = await _userManager.CreateAsync(user, model.Password); + { + UserName = model.Username, + Email = model.Email, + FirstName = model.FirstName, + LastName = model.LastName + }; + + var result = await userManager.CreateAsync(user, model.Password); + if (result.Succeeded) { - if (_siteOptions.Value.EnableEmailConfirmation) + if (siteOptions.Value.EnableEmailConfirmation) { - var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var code = await userManager.GenerateEmailConfirmationTokenAsync(user); + //ControllerExtensions.ShortControllerName(), //TODO: use everywhere ................. - await _emailSender.SendEmailAsync( - user.Email, - "لطفا اکانت خود را تائید کنید", - "~/Areas/Identity/Views/EmailTemplates/_RegisterEmailConfirmation.cshtml", - new RegisterEmailConfirmationViewModel - { - User = user, - EmailConfirmationToken = code, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = - DateTime.UtcNow.ToLongPersianDateTimeString(), - }); + await emailSender.SendEmailAsync(user.Email, subject: "لطفا اکانت خود را تائید کنید", + viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_RegisterEmailConfirmation.cshtml", + new RegisterEmailConfirmationViewModel + { + User = user, + EmailConfirmationToken = code, + EmailSignature = siteOptions.Value.Smtp.FromName, + MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() + }); return RedirectToAction(nameof(ConfirmYourEmail)); } @@ -141,7 +132,7 @@ await _emailSender.SendEmailAsync( return RedirectToAction(nameof(ConfirmedRegisteration)); } - ModelState.AddModelError("", result.DumpErrors(true)); + ModelState.AddModelError(key: "", result.DumpErrors(useHtmlNewLine: true)); } return View(model); diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RolesManagerController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RolesManagerController.cs index 3d0143a..265fcb3 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RolesManagerController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RolesManagerController.cs @@ -10,24 +10,19 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; -[Authorize(Roles = ConstantRoles.Admin), Area(AreaConstants.IdentityArea), - BreadCrumb(Title = "مدیریت نقش‌ها", UseDefaultRouteUrl = true, Order = 0)] -public class RolesManagerController : Controller +[Authorize(Roles = ConstantRoles.Admin)] +[Area(AreaConstants.IdentityArea)] +[BreadCrumb(Title = "مدیریت نقش‌ها", UseDefaultRouteUrl = true, Order = 0)] +public class RolesManagerController(IApplicationRoleManager roleManager) : Controller { private const string RoleNotFound = "نقش درخواستی یافت نشد."; private const int DefaultPageSize = 7; - private readonly IApplicationRoleManager _roleManager; - - public RolesManagerController(IApplicationRoleManager roleManager) - { - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - } - [BreadCrumb(Title = "ایندکس", Order = 1)] public IActionResult Index() { - var roles = _roleManager.GetAllCustomRolesAndUsersCountList(); + var roles = roleManager.GetAllCustomRolesAndUsersCountList(); + return View(roles); } @@ -46,21 +41,28 @@ public async Task RenderRole([FromBody] ModelIdViewModel model) if (model.Id == 0) { - return PartialView("_Create", new RoleViewModel()); + return PartialView(viewName: "_Create", new RoleViewModel()); } - var role = await _roleManager.FindByIdAsync(model.Id.ToString(CultureInfo.InvariantCulture)); + var role = await roleManager.FindByIdAsync(model.Id.ToString(CultureInfo.InvariantCulture)); + if (role == null) { - ModelState.AddModelError("", RoleNotFound); - return PartialView("_Create", new RoleViewModel()); + ModelState.AddModelError(key: "", RoleNotFound); + + return PartialView(viewName: "_Create", new RoleViewModel()); } - return PartialView("_Create", - new RoleViewModel { Id = role.Id.ToString(CultureInfo.InvariantCulture), Name = role.Name }); + return PartialView(viewName: "_Create", new RoleViewModel + { + Id = role.Id.ToString(CultureInfo.InvariantCulture), + Name = role.Name + }); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] public async Task EditRole(RoleViewModel model) { if (model is null) @@ -70,28 +72,35 @@ public async Task EditRole(RoleViewModel model) if (ModelState.IsValid) { - var role = await _roleManager.FindByIdAsync(model.Id); + var role = await roleManager.FindByIdAsync(model.Id); + if (role == null) { - ModelState.AddModelError("", RoleNotFound); + ModelState.AddModelError(key: "", RoleNotFound); } else { role.Name = model.Name; - var result = await _roleManager.UpdateAsync(role); + var result = await roleManager.UpdateAsync(role); + if (result.Succeeded) { - return Json(new { success = true }); + return Json(new + { + success = true + }); } ModelState.AddErrorsFromResult(result); } } - return PartialView("_Create", model); + return PartialView(viewName: "_Create", model); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] public async Task AddRole(RoleViewModel model) { if (model is null) @@ -101,16 +110,20 @@ public async Task AddRole(RoleViewModel model) if (ModelState.IsValid) { - var result = await _roleManager.CreateAsync(new Role(model.Name)); + var result = await roleManager.CreateAsync(new Role(model.Name)); + if (result.Succeeded) { - return Json(new { success = true }); + return Json(new + { + success = true + }); } ModelState.AddErrorsFromResult(result); } - return PartialView("_Create", model); + return PartialView(viewName: "_Create", model); } [AjaxOnly] @@ -123,21 +136,28 @@ public async Task RenderDeleteRole([FromBody] ModelIdViewModel mo if (model == null) { - return BadRequest("model is null."); + return BadRequest(error: "model is null."); } - var role = await _roleManager.FindByIdAsync(model.Id.ToString(CultureInfo.InvariantCulture)); + var role = await roleManager.FindByIdAsync(model.Id.ToString(CultureInfo.InvariantCulture)); + if (role == null) { - ModelState.AddModelError("", RoleNotFound); - return PartialView("_Delete", new RoleViewModel()); + ModelState.AddModelError(key: "", RoleNotFound); + + return PartialView(viewName: "_Delete", new RoleViewModel()); } - return PartialView("_Delete", - new RoleViewModel { Id = role.Id.ToString(CultureInfo.InvariantCulture), Name = role.Name }); + return PartialView(viewName: "_Delete", new RoleViewModel + { + Id = role.Id.ToString(CultureInfo.InvariantCulture), + Name = role.Name + }); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] public async Task Delete(RoleViewModel model) { if (!ModelState.IsValid) @@ -147,44 +167,46 @@ public async Task Delete(RoleViewModel model) if (string.IsNullOrWhiteSpace(model?.Id)) { - return BadRequest("model is null."); + return BadRequest(error: "model is null."); } - var role = await _roleManager.FindByIdAsync(model.Id); + var role = await roleManager.FindByIdAsync(model.Id); + if (role == null) { - ModelState.AddModelError("", RoleNotFound); + ModelState.AddModelError(key: "", RoleNotFound); } else { - var result = await _roleManager.DeleteAsync(role); + var result = await roleManager.DeleteAsync(role); + if (result.Succeeded) { - return Json(new { success = true }); + return Json(new + { + success = true + }); } ModelState.AddErrorsFromResult(result); } - return PartialView("_Delete", model); + return PartialView(viewName: "_Delete", model); } [BreadCrumb(Title = "لیست کاربران دارای نقش", Order = 1)] - public async Task UsersInRole(int? id, int? page = 1, string field = "Id", + public async Task UsersInRole(int? id, + int? page = 1, + string field = "Id", SortOrder order = SortOrder.Ascending) { if (id == null) { - return View("Error"); + return View(viewName: "Error"); } - var model = await _roleManager.GetPagedApplicationUsersInRoleListAsync( - id.Value, - page.Value - 1, - DefaultPageSize, - field, - order, - true); + var model = await roleManager.GetPagedApplicationUsersInRoleListAsync(id.Value, page.Value - 1, DefaultPageSize, + field, order, showAllUsers: true); model.Paging.CurrentPage = page.Value; model.Paging.ItemsPerPage = DefaultPageSize; @@ -192,7 +214,7 @@ public async Task UsersInRole(int? id, int? page = 1, string fiel if (HttpContext.Request.IsAjaxRequest()) { - return PartialView("~/Areas/Identity/Views/UsersManager/_UsersList.cshtml", model); + return PartialView(viewName: "~/Areas/Identity/Views/UsersManager/_UsersList.cshtml", model); } return View(model); diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/SystemLogController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/SystemLogController.cs index faea996..005c8d0 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/SystemLogController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/SystemLogController.cs @@ -7,61 +7,64 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; -[Authorize(Roles = ConstantRoles.Admin), Area(AreaConstants.IdentityArea), - BreadCrumb(Title = "لاگ سیستم", UseDefaultRouteUrl = true, Order = 0)] -public class SystemLogController : Controller +[Authorize(Roles = ConstantRoles.Admin)] +[Area(AreaConstants.IdentityArea)] +[BreadCrumb(Title = "لاگ سیستم", UseDefaultRouteUrl = true, Order = 0)] +public class SystemLogController(IAppLogItemsService appLogItemsService) : Controller { - private readonly IAppLogItemsService _appLogItemsService; - - public SystemLogController( - IAppLogItemsService appLogItemsService) - { - _appLogItemsService = appLogItemsService ?? throw new ArgumentNullException(nameof(appLogItemsService)); - } + private readonly IAppLogItemsService _appLogItemsService = + appLogItemsService ?? throw new ArgumentNullException(nameof(appLogItemsService)); [BreadCrumb(Title = "ایندکس", Order = 1)] - public async Task Index( - string logLevel = "", + public async Task Index(string logLevel = "", int pageNumber = 1, int pageSize = -1, string sort = "desc") { var itemsPerPage = 10; + if (pageSize > 0) { itemsPerPage = pageSize; } - var model = await _appLogItemsService.GetPagedAppLogItemsAsync( - pageNumber, itemsPerPage, - string.Equals(sort, "desc", StringComparison.OrdinalIgnoreCase) + var model = await _appLogItemsService.GetPagedAppLogItemsAsync(pageNumber, itemsPerPage, + string.Equals(sort, b: "desc", StringComparison.OrdinalIgnoreCase) ? SortOrder.Descending : SortOrder.Ascending, logLevel); + model.LogLevel = logLevel; model.Paging.CurrentPage = pageNumber; model.Paging.ItemsPerPage = itemsPerPage; + return View(model); } - [HttpPost, ValidateAntiForgeryToken] + [HttpPost] + [ValidateAntiForgeryToken] public async Task LogItemDelete(int id) { await _appLogItemsService.DeleteAsync(id); + return RedirectToAction(nameof(Index)); } - [HttpPost, ValidateAntiForgeryToken] + [HttpPost] + [ValidateAntiForgeryToken] public async Task LogDeleteAll(string logLevel = "") { await _appLogItemsService.DeleteAllAsync(logLevel); + return RedirectToAction(nameof(Index)); } - [HttpPost, ValidateAntiForgeryToken] + [HttpPost] + [ValidateAntiForgeryToken] public async Task LogDeleteOlderThan(string logLevel = "", int days = 5) { var cutoffUtc = DateTime.UtcNow.AddDays(-days); await _appLogItemsService.DeleteOlderThanAsync(cutoffUtc, logLevel); + return RedirectToAction(nameof(Index)); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/TwoFactorController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/TwoFactorController.cs index d61db0c..faded11 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/TwoFactorController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/TwoFactorController.cs @@ -10,83 +10,79 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; -[Authorize, Area(AreaConstants.IdentityArea), - BreadCrumb(Title = "اعتبارسنجی دو مرحله‌ای", UseDefaultRouteUrl = true, Order = 0)] -public class TwoFactorController : Controller +[Authorize] +[Area(AreaConstants.IdentityArea)] +[BreadCrumb(Title = "اعتبارسنجی دو مرحله‌ای", UseDefaultRouteUrl = true, Order = 0)] +public class TwoFactorController( + IApplicationUserManager userManager, + IApplicationSignInManager signInManager, + IEmailSender emailSender, + IOptionsSnapshot siteOptions) : Controller { - private readonly IEmailSender _emailSender; - private readonly IApplicationSignInManager _signInManager; - private readonly IOptionsSnapshot _siteOptions; - private readonly IApplicationUserManager _userManager; - - public TwoFactorController( - IApplicationUserManager userManager, - IApplicationSignInManager signInManager, - IEmailSender emailSender, - IOptionsSnapshot siteOptions) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); - _emailSender = emailSender ?? throw new ArgumentNullException(nameof(emailSender)); - _siteOptions = siteOptions ?? throw new ArgumentNullException(nameof(siteOptions)); - } - - [AllowAnonymous, BreadCrumb(Title = "ارسال کد", Order = 1)] + [AllowAnonymous] + [BreadCrumb(Title = "ارسال کد", Order = 1)] public async Task SendCode(string returnUrl = null, bool rememberMe = false) { - var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + var user = await signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) { - return View("NotFound"); + return View(viewName: "NotFound"); } var tokenProvider = "Email"; - var code = await _userManager.GenerateTwoFactorTokenAsync(user, tokenProvider); + var code = await userManager.GenerateTwoFactorTokenAsync(user, tokenProvider); + if (string.IsNullOrWhiteSpace(code)) { - return View("Error"); + return View(viewName: "Error"); } - await _emailSender.SendEmailAsync( - user.Email, - "کد جدید اعتبارسنجی دو مرحله‌ای", - "~/Areas/Identity/Views/EmailTemplates/_TwoFactorSendCode.cshtml", + await emailSender.SendEmailAsync(user.Email, subject: "کد جدید اعتبارسنجی دو مرحله‌ای", + viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_TwoFactorSendCode.cshtml", new TwoFactorSendCodeViewModel { Token = code, - EmailSignature = _siteOptions.Value.Smtp.FromName, + EmailSignature = siteOptions.Value.Smtp.FromName, MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() }); - return RedirectToAction( - nameof(VerifyCode), - new - { - Provider = tokenProvider, - ReturnUrl = returnUrl, - RememberMe = rememberMe - }); + return RedirectToAction(nameof(VerifyCode), new + { + Provider = tokenProvider, + ReturnUrl = returnUrl, + RememberMe = rememberMe + }); } - [AllowAnonymous, BreadCrumb(Title = "تائید کد", Order = 1)] + [AllowAnonymous] + [BreadCrumb(Title = "تائید کد", Order = 1)] public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) { // Require that the user has already logged in via username/password or external login - var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + var user = await signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) { - return View("NotFound"); + return View(viewName: "NotFound"); } - return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + return View(new VerifyCodeViewModel + { + Provider = provider, + ReturnUrl = returnUrl, + RememberMe = rememberMe + }); } - [HttpPost, AllowAnonymous, ValidateAntiForgeryToken] + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] public async Task VerifyCode(VerifyCodeViewModel model) { if (model is null) { - return View("Error"); + return View(viewName: "Error"); } if (!ModelState.IsValid) @@ -97,29 +93,28 @@ public async Task VerifyCode(VerifyCodeViewModel model) // The following code protects for brute force attacks against the two factor codes. // If a user enters incorrect codes for a specified amount of time then the user account // will be locked out for a specified amount of time. - var result = await _signInManager.TwoFactorSignInAsync( - model.Provider, - model.Code, - model.RememberMe, - model.RememberBrowser); + var result = await signInManager.TwoFactorSignInAsync( + model.Provider, model.Code, model.RememberMe, model.RememberBrowser); if (result.Succeeded) { var returnUrl = model.ReturnUrl; + if (!string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } - return RedirectToAction(nameof(HomeController.Index), "Home"); + return RedirectToAction(nameof(HomeController.Index), controllerName: "Home"); } if (result.IsLockedOut) { - return View("Lockout"); + return View(viewName: "Lockout"); } - ModelState.AddModelError(string.Empty, "کد وارد شده غیر معتبر است."); + ModelState.AddModelError(string.Empty, errorMessage: "کد وارد شده غیر معتبر است."); + return View(model); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserCardController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserCardController.cs index 642eb33..6dfdd9d 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserCardController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserCardController.cs @@ -8,21 +8,11 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; -[AllowAnonymous, Area(AreaConstants.IdentityArea), - BreadCrumb(Title = "برگه‌ی کاربری", UseDefaultRouteUrl = true, Order = 0)] -public class UserCardController : Controller +[AllowAnonymous] +[Area(AreaConstants.IdentityArea)] +[BreadCrumb(Title = "برگه‌ی کاربری", UseDefaultRouteUrl = true, Order = 0)] +public class UserCardController(IApplicationUserManager userManager, IApplicationRoleManager roleManager) : Controller { - private readonly IApplicationRoleManager _roleManager; - private readonly IApplicationUserManager _userManager; - - public UserCardController( - IApplicationUserManager userManager, - IApplicationRoleManager roleManager) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - } - [BreadCrumb(Title = "ایندکس", Order = 1)] public async Task Index(int? id) { @@ -33,22 +23,24 @@ public async Task Index(int? id) if (!id.HasValue) { - return View("Error"); + return View(viewName: "Error"); } - var user = await _userManager.FindByIdIncludeUserRolesAsync(id.Value); + var user = await userManager.FindByIdIncludeUserRolesAsync(id.Value); + if (user == null) { - return View("NotFound"); + return View(viewName: "NotFound"); } var model = new UserCardItemViewModel { User = user, ShowAdminParts = User.IsInRole(ConstantRoles.Admin), - Roles = await _roleManager.GetAllCustomRolesAsync(), + Roles = await roleManager.GetAllCustomRolesAsync(), ActiveTab = UserCardItemActiveTab.UserInfo }; + return View(model); } @@ -60,13 +52,11 @@ public async Task EmailToImage(int? id) return NotFound(); } - var fileContents = await _userManager.GetEmailImageAsync(id); - return new FileContentResult(fileContents, "image/png"); + var fileContents = await userManager.GetEmailImageAsync(id); + + return new FileContentResult(fileContents, contentType: "image/png"); } [BreadCrumb(Title = "لیست کاربران آنلاین", Order = 1)] - public IActionResult OnlineUsers() - { - return View(); - } + public IActionResult OnlineUsers() => View(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserProfileController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserProfileController.cs index 3e4e521..1bfd3a4 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserProfileController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserProfileController.cs @@ -18,45 +18,18 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; [Authorize] [Area(AreaConstants.IdentityArea)] [BreadCrumb(Title = "مشخصات کاربری", UseDefaultRouteUrl = true, Order = 0)] -public class UserProfileController : Controller +public class UserProfileController( + IApplicationUserManager userManager, + IApplicationRoleManager roleManager, + IApplicationSignInManager signInManager, + IProtectionProviderService protectionProviderService, + IUserValidator userValidator, + IUsedPasswordsService usedPasswordsService, + IUsersPhotoService usersPhotoService, + IOptionsSnapshot siteOptions, + IEmailSender emailSender, + ILogger logger) : Controller { - private readonly IEmailSender _emailSender; - private readonly ILogger _logger; - private readonly IProtectionProviderService _protectionProviderService; - private readonly IApplicationRoleManager _roleManager; - private readonly IApplicationSignInManager _signInManager; - private readonly IOptionsSnapshot _siteOptions; - private readonly IUsedPasswordsService _usedPasswordsService; - private readonly IApplicationUserManager _userManager; - private readonly IUsersPhotoService _usersPhotoService; - private readonly IUserValidator _userValidator; - - public UserProfileController(IApplicationUserManager userManager, - IApplicationRoleManager roleManager, - IApplicationSignInManager signInManager, - IProtectionProviderService protectionProviderService, - IUserValidator userValidator, - IUsedPasswordsService usedPasswordsService, - IUsersPhotoService usersPhotoService, - IOptionsSnapshot siteOptions, - IEmailSender emailSender, - ILogger logger) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); - - _protectionProviderService = protectionProviderService ?? - throw new ArgumentNullException(nameof(protectionProviderService)); - - _userValidator = userValidator ?? throw new ArgumentNullException(nameof(userValidator)); - _usedPasswordsService = usedPasswordsService ?? throw new ArgumentNullException(nameof(usedPasswordsService)); - _usersPhotoService = usersPhotoService ?? throw new ArgumentNullException(nameof(usersPhotoService)); - _siteOptions = siteOptions ?? throw new ArgumentNullException(nameof(siteOptions)); - _emailSender = emailSender ?? throw new ArgumentNullException(nameof(emailSender)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - [Authorize(Roles = ConstantRoles.Admin)] [BreadCrumb(Title = "ایندکس", Order = 1)] public async Task AdminEdit(int? id) @@ -66,7 +39,7 @@ public async Task AdminEdit(int? id) return View(viewName: "Error"); } - var user = await _userManager.FindByIdAsync(id.Value.ToString(CultureInfo.InvariantCulture)); + var user = await userManager.FindByIdAsync(id.Value.ToString(CultureInfo.InvariantCulture)); return await RenderForm(user, isAdminEdit: true); } @@ -74,7 +47,7 @@ public async Task AdminEdit(int? id) [BreadCrumb(Title = "ایندکس", Order = 1)] public async Task Index() { - var user = await _userManager.GetCurrentUserAsync(); + var user = await userManager.GetCurrentUserAsync(); return await RenderForm(user, isAdminEdit: false); } @@ -90,22 +63,22 @@ public async Task Index(UserProfileViewModel model) if (ModelState.IsValid) { - var pid = _protectionProviderService.Decrypt(model.Pid); + var pid = protectionProviderService.Decrypt(model.Pid); if (string.IsNullOrWhiteSpace(pid)) { return View(viewName: "Error"); } - if (!string.Equals(pid, _userManager.GetCurrentUserId(), StringComparison.Ordinal) && - !_roleManager.IsCurrentUserInRole(ConstantRoles.Admin)) + if (!string.Equals(pid, userManager.GetCurrentUserId(), StringComparison.Ordinal) && + !roleManager.IsCurrentUserInRole(ConstantRoles.Admin)) { - _logger.LogWarningMessage($"سعی در دسترسی غیرمجاز به ویرایش اطلاعات کاربر {pid}"); + logger.LogWarningMessage($"سعی در دسترسی غیرمجاز به ویرایش اطلاعات کاربر {pid}"); return View(viewName: "Error"); } - var user = await _userManager.FindByIdAsync(pid); + var user = await userManager.FindByIdAsync(pid); if (user == null) { @@ -135,22 +108,22 @@ public async Task Index(UserProfileViewModel model) return View(nameof(Index), model); } - var updateResult = await _userManager.UpdateAsync(user); + var updateResult = await userManager.UpdateAsync(user); if (updateResult.Succeeded) { if (!model.IsAdminEdit) { // reflect the changes in the current user's Identity cookie - await _signInManager.RefreshSignInAsync(user); + await signInManager.RefreshSignInAsync(user); } - await _emailSender.SendEmailAsync(user.Email, subject: "اطلاع رسانی به روز رسانی مشخصات کاربری", + await emailSender.SendEmailAsync(user.Email, subject: "اطلاع رسانی به روز رسانی مشخصات کاربری", viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_UserProfileUpdateNotification.cshtml", new UserProfileUpdateNotificationViewModel { User = user, - EmailSignature = _siteOptions.Value.Smtp.FromName, + EmailSignature = siteOptions.Value.Smtp.FromName, MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() }); @@ -175,18 +148,18 @@ await _emailSender.SendEmailAsync(user.Email, subject: "اطلاع رسانی ب [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ValidateUsername(string username, string email, string pid) { - pid = _protectionProviderService.Decrypt(pid); + pid = protectionProviderService.Decrypt(pid); if (string.IsNullOrWhiteSpace(pid)) { return Json(data: "اطلاعات وارد شده معتبر نیست."); } - var user = await _userManager.FindByIdAsync(pid); + var user = await userManager.FindByIdAsync(pid); user.UserName = username; user.Email = email; - var result = await _userValidator.ValidateAsync((UserManager)_userManager, user); + var result = await userValidator.ValidateAsync((UserManager)userManager, user); return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); } @@ -208,7 +181,7 @@ private static void UpdateUserBirthDate(UserProfileViewModel model, User user) private async Task RenderForm(User user, bool isAdminEdit) { - _usersPhotoService.SetUserDefaultPhoto(user); + usersPhotoService.SetUserDefaultPhoto(user); var userProfile = new UserProfileViewModel { @@ -219,10 +192,10 @@ private async Task RenderForm(User user, bool isAdminEdit) UserName = user.UserName, FirstName = user.FirstName, LastName = user.LastName, - Pid = _protectionProviderService.Encrypt(user.Id.ToString(CultureInfo.InvariantCulture)), + Pid = protectionProviderService.Encrypt(user.Id.ToString(CultureInfo.InvariantCulture)), IsEmailPublic = user.IsEmailPublic, TwoFactorEnabled = user.TwoFactorEnabled, - IsPasswordTooOld = await _usedPasswordsService.IsLastUserPasswordTooOldAsync(user.Id) + IsPasswordTooOld = await usedPasswordsService.IsLastUserPasswordTooOldAsync(user.Id) }; if (user.BirthDate.HasValue) @@ -238,13 +211,13 @@ private async Task RenderForm(User user, bool isAdminEdit) private async Task UpdateUserAvatarImage(UserProfileViewModel model, User user) { - _usersPhotoService.SetUserDefaultPhoto(user); + usersPhotoService.SetUserDefaultPhoto(user); var photoFile = model.Photo; if (photoFile is not null && photoFile.Length > 0) { - var imageOptions = _siteOptions.Value.UserAvatarImageOptions; + var imageOptions = siteOptions.Value.UserAvatarImageOptions; if (!photoFile.IsValidImageFile(imageOptions.MaxWidth, imageOptions.MaxHeight)) { @@ -257,7 +230,7 @@ private async Task UpdateUserAvatarImage(UserProfileViewModel model, User return false; } - var uploadsRootFolder = _usersPhotoService.GetUsersAvatarsFolderPath(); + var uploadsRootFolder = usersPhotoService.GetUsersAvatarsFolderPath(); var photoFileName = Invariant($"{user.Id}{Path.GetExtension(photoFile.FileName)}"); var filePath = Path.Combine(uploadsRootFolder, photoFileName); @@ -277,26 +250,26 @@ private async Task UpdateUserEmail(UserProfileViewModel model, User user) if (!string.Equals(user.Email, model.Email, StringComparison.Ordinal)) { user.Email = model.Email; - var userValidator = await _userValidator.ValidateAsync((UserManager)_userManager, user); + var validator = await userValidator.ValidateAsync((UserManager)userManager, user); - if (!userValidator.Succeeded) + if (!validator.Succeeded) { - ModelState.AddModelError(key: "", userValidator.DumpErrors(useHtmlNewLine: true)); + ModelState.AddModelError(key: "", validator.DumpErrors(useHtmlNewLine: true)); return false; } user.EmailConfirmed = false; - var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var code = await userManager.GenerateEmailConfirmationTokenAsync(user); - await _emailSender.SendEmailAsync(user.Email, subject: "لطفا اکانت خود را تائید کنید", + await emailSender.SendEmailAsync(user.Email, subject: "لطفا اکانت خود را تائید کنید", viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_RegisterEmailConfirmation.cshtml", new RegisterEmailConfirmationViewModel { User = user, EmailConfirmationToken = code, - EmailSignature = _siteOptions.Value.Smtp.FromName, + EmailSignature = siteOptions.Value.Smtp.FromName, MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() }); } @@ -309,11 +282,11 @@ private async Task UpdateUserName(UserProfileViewModel model, User user) if (!string.Equals(user.UserName, model.UserName, StringComparison.Ordinal)) { user.UserName = model.UserName; - var userValidator = await _userValidator.ValidateAsync((UserManager)_userManager, user); + var validator = await userValidator.ValidateAsync((UserManager)userManager, user); - if (!userValidator.Succeeded) + if (!validator.Succeeded) { - ModelState.AddModelError(key: "", userValidator.DumpErrors(useHtmlNewLine: true)); + ModelState.AddModelError(key: "", validator.DumpErrors(useHtmlNewLine: true)); return false; } diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UsersManagerController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UsersManagerController.cs index a7220ba..04d34cf 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UsersManagerController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UsersManagerController.cs @@ -10,122 +10,136 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; -[Authorize(Roles = ConstantRoles.Admin), Area(AreaConstants.IdentityArea), - BreadCrumb(Title = "مدیریت کاربران", UseDefaultRouteUrl = true, Order = 0)] -public class UsersManagerController : Controller +[Authorize(Roles = ConstantRoles.Admin)] +[Area(AreaConstants.IdentityArea)] +[BreadCrumb(Title = "مدیریت کاربران", UseDefaultRouteUrl = true, Order = 0)] +public class UsersManagerController(IApplicationUserManager userManager, IApplicationRoleManager roleManager) + : Controller { private const int DefaultPageSize = 7; - private readonly IApplicationRoleManager _roleManager; - private readonly IApplicationUserManager _userManager; - - public UsersManagerController( - IApplicationUserManager userManager, - IApplicationRoleManager roleManager) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - } - - [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ActivateUserEmailStat(int userId) { User thisUser = null; - var result = await _userManager.UpdateUserAndSecurityStampAsync( - userId, user => - { - user.EmailConfirmed = true; - thisUser = user; - }); + + var result = await userManager.UpdateUserAndSecurityStampAsync(userId, user => + { + user.EmailConfirmed = true; + thisUser = user; + }); + if (!result.Succeeded) { - return BadRequest(result.DumpErrors(true)); + return BadRequest(result.DumpErrors(useHtmlNewLine: true)); } return await ReturnUserCardPartialView(thisUser); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ChangeUserLockoutMode(int userId, bool activate) { User thisUser = null; - var result = await _userManager.UpdateUserAndSecurityStampAsync( - userId, user => - { - user.LockoutEnabled = activate; - thisUser = user; - }); + + var result = await userManager.UpdateUserAndSecurityStampAsync(userId, user => + { + user.LockoutEnabled = activate; + thisUser = user; + }); + if (!result.Succeeded) { - return BadRequest(result.DumpErrors(true)); + return BadRequest(result.DumpErrors(useHtmlNewLine: true)); } return await ReturnUserCardPartialView(thisUser); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ChangeUserRoles(int userId, int[] roleIds) { User thisUser = null; - var result = await _userManager.AddOrUpdateUserRolesAsync( - userId, roleIds, user => thisUser = user); + var result = await userManager.AddOrUpdateUserRolesAsync(userId, roleIds, user => thisUser = user); + if (!result.Succeeded) { - return BadRequest(result.DumpErrors(true)); + return BadRequest(result.DumpErrors(useHtmlNewLine: true)); } return await ReturnUserCardPartialView(thisUser); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ChangeUserStat(int userId, bool activate) { User thisUser = null; - var result = await _userManager.UpdateUserAndSecurityStampAsync( - userId, user => - { - user.IsActive = activate; - thisUser = user; - }); + + var result = await userManager.UpdateUserAndSecurityStampAsync(userId, user => + { + user.IsActive = activate; + thisUser = user; + }); + if (!result.Succeeded) { - return BadRequest(result.DumpErrors(true)); + return BadRequest(result.DumpErrors(useHtmlNewLine: true)); } return await ReturnUserCardPartialView(thisUser); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task ChangeUserTwoFactorAuthenticationStat(int userId, bool activate) { User thisUser = null; - var result = await _userManager.UpdateUserAndSecurityStampAsync( - userId, user => - { - user.TwoFactorEnabled = activate; - thisUser = user; - }); + + var result = await userManager.UpdateUserAndSecurityStampAsync(userId, user => + { + user.TwoFactorEnabled = activate; + thisUser = user; + }); + if (!result.Succeeded) { - return BadRequest(result.DumpErrors(true)); + return BadRequest(result.DumpErrors(useHtmlNewLine: true)); } return await ReturnUserCardPartialView(thisUser); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task EndUserLockout(int userId) { User thisUser = null; - var result = await _userManager.UpdateUserAndSecurityStampAsync( - userId, user => - { - user.LockoutEnd = null; - thisUser = user; - }); + + var result = await userManager.UpdateUserAndSecurityStampAsync(userId, user => + { + user.LockoutEnd = null; + thisUser = user; + }); + if (!result.Succeeded) { - return BadRequest(result.DumpErrors(true)); + return BadRequest(result.DumpErrors(useHtmlNewLine: true)); } return await ReturnUserCardPartialView(thisUser); @@ -134,12 +148,8 @@ public async Task EndUserLockout(int userId) [BreadCrumb(Title = "ایندکس", Order = 1)] public async Task Index(int? page = 1, string field = "Id", SortOrder order = SortOrder.Ascending) { - var model = await _userManager.GetPagedUsersListAsync( - page.Value - 1, - DefaultPageSize, - field, - order, - true); + var model = await userManager.GetPagedUsersListAsync(page.Value - 1, DefaultPageSize, field, order, + showAllUsers: true); model.Paging.CurrentPage = page.Value; model.Paging.ItemsPerPage = DefaultPageSize; @@ -147,13 +157,16 @@ public async Task Index(int? page = 1, string field = "Id", SortO if (HttpContext.Request.IsAjaxRequest()) { - return PartialView("_UsersList", model); + return PartialView(viewName: "_UsersList", model); } return View(model); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + [AjaxOnly] + [HttpPost] + [ValidateAntiForgeryToken] + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task SearchUsers(SearchUsersViewModel model) { if (model is null) @@ -161,28 +174,27 @@ public async Task SearchUsers(SearchUsersViewModel model) return BadRequest(); } - var pagedUsersList = await _userManager.GetPagedUsersListAsync( - model, - 0); + var pagedUsersList = await userManager.GetPagedUsersListAsync(model, pageNumber: 0); pagedUsersList.Paging.CurrentPage = 1; pagedUsersList.Paging.ItemsPerPage = model.MaxNumberOfRows; pagedUsersList.Paging.ShowFirstLast = true; model.PagedUsersList = pagedUsersList; - return PartialView("_SearchUsers", model); + + return PartialView(viewName: "_SearchUsers", model); } private async Task ReturnUserCardPartialView(User thisUser) { - var roles = await _roleManager.GetAllCustomRolesAsync(); - return PartialView("~/Areas/Identity/Views/UserCard/_UserCardItem.cshtml", - new UserCardItemViewModel - { - User = thisUser, - ShowAdminParts = true, - Roles = roles, - ActiveTab = UserCardItemActiveTab.UserAdmin - }); + var roles = await roleManager.GetAllCustomRolesAsync(); + + return PartialView(viewName: "~/Areas/Identity/Views/UserCard/_UserCardItem.cshtml", new UserCardItemViewModel + { + User = thisUser, + ShowAdminParts = true, + Roles = roles, + ActiveTab = UserCardItemActiveTab.UserAdmin + }); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/SecurityTrimmingTagHelper.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/SecurityTrimmingTagHelper.cs index 135d528..bc48730 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/SecurityTrimmingTagHelper.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/SecurityTrimmingTagHelper.cs @@ -9,36 +9,31 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.TagHelpers; /// More info: http://www.dntips.ir/post/2527 /// And http://www.dntips.ir/post/2581 /// -[HtmlTargetElement("security-trimming")] -public class SecurityTrimmingTagHelper : TagHelper +[HtmlTargetElement(tag: "security-trimming")] +public class SecurityTrimmingTagHelper(ISecurityTrimmingService securityTrimmingService) : TagHelper { - private readonly ISecurityTrimmingService _securityTrimmingService; - - public SecurityTrimmingTagHelper(ISecurityTrimmingService securityTrimmingService) - { - _securityTrimmingService = - securityTrimmingService ?? throw new ArgumentNullException(nameof(securityTrimmingService)); - } + private readonly ISecurityTrimmingService _securityTrimmingService = + securityTrimmingService ?? throw new ArgumentNullException(nameof(securityTrimmingService)); /// /// The name of the action method. /// - [HtmlAttributeName("asp-action")] + [HtmlAttributeName(name: "asp-action")] public string Action { get; set; } /// /// The name of the area. /// - [HtmlAttributeName("asp-area")] + [HtmlAttributeName(name: "asp-area")] public string Area { get; set; } /// /// The name of the controller. /// - [HtmlAttributeName("asp-controller")] + [HtmlAttributeName(name: "asp-controller")] public string Controller { get; set; } - [ViewContext, HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } + [ViewContext] [HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/VisibilityTagHelper.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/VisibilityTagHelper.cs index 8a58abd..a54f47f 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/VisibilityTagHelper.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/VisibilityTagHelper.cs @@ -6,13 +6,13 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.TagHelpers; /// More info: http://www.dntips.ir/post/2527 /// And http://www.dntips.ir/post/2581 /// -[HtmlTargetElement("div")] +[HtmlTargetElement(tag: "div")] public class VisibilityTagHelper : TagHelper { /// /// default to true otherwise all existing target elements will not be shown, because bool's default to false /// - [HtmlAttributeName("asp-is-visible")] + [HtmlAttributeName(name: "asp-is-visible")] public bool IsVisible { get; set; } = true; public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/OnlineUsersViewComponent.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/OnlineUsersViewComponent.cs index 20dd18a..22bbe4d 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/OnlineUsersViewComponent.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/OnlineUsersViewComponent.cs @@ -4,19 +4,13 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.ViewComponents; -public class OnlineUsersViewComponent : ViewComponent +public class OnlineUsersViewComponent(ISiteStatService siteStatService) : ViewComponent { - private readonly ISiteStatService _siteStatService; - - public OnlineUsersViewComponent(ISiteStatService siteStatService) - { - _siteStatService = siteStatService; - } - public async Task InvokeAsync(int numbersToTake, int minutesToTake, bool showMoreItemsLink) { - var usersList = await _siteStatService.GetOnlineUsersListAsync(numbersToTake, minutesToTake); - return View("~/Areas/Identity/Views/Shared/Components/OnlineUsers/Default.cshtml", + var usersList = await siteStatService.GetOnlineUsersListAsync(numbersToTake, minutesToTake); + + return View(viewName: "~/Areas/Identity/Views/Shared/Components/OnlineUsers/Default.cshtml", new OnlineUsersViewModel { MinutesToTake = minutesToTake, diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/TodayBirthDaysViewComponent.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/TodayBirthDaysViewComponent.cs index 65527eb..3ac300b 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/TodayBirthDaysViewComponent.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/TodayBirthDaysViewComponent.cs @@ -4,21 +4,14 @@ namespace ASPNETCoreIdentitySample.Areas.Identity.ViewComponents; -public class TodayBirthDaysViewComponent : ViewComponent +public class TodayBirthDaysViewComponent(ISiteStatService siteStatService) : ViewComponent { - private readonly ISiteStatService _siteStatService; - - public TodayBirthDaysViewComponent(ISiteStatService siteStatService) - { - _siteStatService = siteStatService; - } - public async Task InvokeAsync() { - var usersList = await _siteStatService.GetTodayBirthdayListAsync(); - var usersAverageAge = await _siteStatService.GetUsersAverageAge(); + var usersList = await siteStatService.GetTodayBirthdayListAsync(); + var usersAverageAge = await siteStatService.GetUsersAverageAge(); - return View("~/Areas/Identity/Views/Shared/Components/TodayBirthDays/Default.cshtml", + return View(viewName: "~/Areas/Identity/Views/Shared/Components/TodayBirthDays/Default.cshtml", new TodayBirthDaysViewModel { Users = usersList, diff --git a/src/ASPNETCoreIdentitySample/Areas/Test/Controllers/DynamicPermissionsAreaSampleController.cs b/src/ASPNETCoreIdentitySample/Areas/Test/Controllers/DynamicPermissionsAreaSampleController.cs index f0b4afd..bf3763a 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Test/Controllers/DynamicPermissionsAreaSampleController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Test/Controllers/DynamicPermissionsAreaSampleController.cs @@ -9,14 +9,13 @@ namespace ASPNETCoreIdentitySample.Areas.Test.Controllers; /// /// More info: http://www.dntips.ir/post/2581 /// -[Authorize(Policy = ConstantPolicies.DynamicPermission), Area(TestAreaConstants.TestArea), - BreadCrumb(UseDefaultRouteUrl = true, Order = 0), - DisplayName("کنترلر نمونه با سطح دسترسی پویا در یک ناحیه خاص آزمایشی")] +[Authorize(Policy = ConstantPolicies.DynamicPermission)] +[Area(TestAreaConstants.TestArea)] +[BreadCrumb(UseDefaultRouteUrl = true, Order = 0)] +[DisplayName(displayName: "کنترلر نمونه با سطح دسترسی پویا در یک ناحیه خاص آزمایشی")] public class DynamicPermissionsAreaSampleController : Controller { - [DisplayName("ایندکس"), BreadCrumb(Order = 1)] - public IActionResult Index() - { - return View(); - } + [DisplayName(displayName: "ایندکس")] + [BreadCrumb(Order = 1)] + public IActionResult Index() => View(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsSampleController.cs b/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsSampleController.cs index 3e8ac0c..6d82305 100644 --- a/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsSampleController.cs +++ b/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsSampleController.cs @@ -10,43 +10,30 @@ namespace ASPNETCoreIdentitySample.Controllers; /// /// More info: http://www.dntips.ir/post/2581 /// -[Authorize(Policy = ConstantPolicies.DynamicPermission), BreadCrumb(UseDefaultRouteUrl = true, Order = 0), - DisplayName("کنترلر نمونه با سطح دسترسی پویا")] +[Authorize(Policy = ConstantPolicies.DynamicPermission)] +[BreadCrumb(UseDefaultRouteUrl = true, Order = 0)] +[DisplayName(displayName: "کنترلر نمونه با سطح دسترسی پویا")] public class DynamicPermissionsSampleController : Controller { - [DisplayName("ایندکس"), BreadCrumb(Order = 1)] - public IActionResult Index() - { - return View(); - } + [DisplayName(displayName: "ایندکس")] + [BreadCrumb(Order = 1)] + public IActionResult Index() => View(); - [HttpPost, ValidateAntiForgeryToken] - public IActionResult Index(RoleViewModel model) - { - return View(model); - } + [HttpPost] [ValidateAntiForgeryToken] public IActionResult Index(RoleViewModel model) => View(model); - [DisplayName("گزارش از لیست کتاب‌ها"), BreadCrumb(Order = 1)] - public IActionResult Books() - { - return View("Index"); - } + [DisplayName(displayName: "گزارش از لیست کتاب‌ها")] + [BreadCrumb(Order = 1)] + public IActionResult Books() => View(viewName: "Index"); - [DisplayName("گزارش از لیست مراجعان"), BreadCrumb(Order = 1)] - public IActionResult Users() - { - return View("Index"); - } + [DisplayName(displayName: "گزارش از لیست مراجعان")] + [BreadCrumb(Order = 1)] + public IActionResult Users() => View(viewName: "Index"); - [DisplayName("گزارش از لیست امانات"), BreadCrumb(Order = 1)] - public IActionResult BooksGiven() - { - return View("Index"); - } + [DisplayName(displayName: "گزارش از لیست امانات")] + [BreadCrumb(Order = 1)] + public IActionResult BooksGiven() => View(viewName: "Index"); - [DisplayName("گزارش از لیست مفقودی‌ها"), BreadCrumb(Order = 1)] - public IActionResult BooksMissings() - { - return View("Index"); - } + [DisplayName(displayName: "گزارش از لیست مفقودی‌ها")] + [BreadCrumb(Order = 1)] + public IActionResult BooksMissings() => View(viewName: "Index"); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsTestController.cs b/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsTestController.cs index c91c457..c67cd16 100644 --- a/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsTestController.cs +++ b/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsTestController.cs @@ -10,44 +10,31 @@ namespace ASPNETCoreIdentitySample.Controllers; /// /// More info: http://www.dntips.ir/post/2581 /// -[Authorize(Policy = ConstantPolicies.DynamicPermission), BreadCrumb(UseDefaultRouteUrl = true, Order = 0), - DisplayName("کنترلر آزمایشی با سطح دسترسی پویا")] -// [NoBrowserCache] +[Authorize(Policy = ConstantPolicies.DynamicPermission)] +[BreadCrumb(UseDefaultRouteUrl = true, Order = 0)] +[DisplayName(displayName: "کنترلر آزمایشی با سطح دسترسی پویا")] public class DynamicPermissionsTestController : Controller { - [DisplayName("ایندکس"), BreadCrumb(Order = 1)] - public IActionResult Index() - { - return View(); - } + [DisplayName(displayName: "ایندکس")] + [BreadCrumb(Order = 1)] + public IActionResult Index() => View(); [HttpPost] // More info: http://www.dntips.ir/post/2468/ and http://www.dntips.ir/post/2470/ - public IActionResult Index([FromBody] RoleViewModel model) - { - return Json(model); - } + public IActionResult Index([FromBody] RoleViewModel model) => Json(model); - [DisplayName("گزارش از لیست محصولات"), BreadCrumb(Order = 1)] - public IActionResult Products() - { - return View("Index"); - } + [DisplayName(displayName: "گزارش از لیست محصولات")] + [BreadCrumb(Order = 1)] + public IActionResult Products() => View(viewName: "Index"); - [DisplayName("گزارش از لیست سفارشات"), BreadCrumb(Order = 1)] - public IActionResult Orders() - { - return View("Index"); - } + [DisplayName(displayName: "گزارش از لیست سفارشات")] + [BreadCrumb(Order = 1)] + public IActionResult Orders() => View(viewName: "Index"); - [DisplayName("گزارش از لیست فروش"), BreadCrumb(Order = 1)] - public IActionResult Sells() - { - return View("Index"); - } + [DisplayName(displayName: "گزارش از لیست فروش")] + [BreadCrumb(Order = 1)] + public IActionResult Sells() => View(viewName: "Index"); - [DisplayName("گزارش از لیست خریداران"), BreadCrumb(Order = 1)] - public IActionResult Customers() - { - return View("Index"); - } + [DisplayName(displayName: "گزارش از لیست خریداران")] + [BreadCrumb(Order = 1)] + public IActionResult Customers() => View(viewName: "Index"); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Controllers/ErrorController.cs b/src/ASPNETCoreIdentitySample/Controllers/ErrorController.cs index 1ecfcc0..aafa74c 100644 --- a/src/ASPNETCoreIdentitySample/Controllers/ErrorController.cs +++ b/src/ASPNETCoreIdentitySample/Controllers/ErrorController.cs @@ -7,15 +7,8 @@ namespace ASPNETCoreIdentitySample.Controllers; [BreadCrumb(Title = "خطا", UseDefaultRouteUrl = true, Order = 0, GlyphIcon = "fas fa-warning")] -public class ErrorController : Controller +public class ErrorController(ILogger logger) : Controller { - private readonly ILogger _logger; - - public ErrorController(ILogger logger) - { - _logger = logger; - } - /// /// More info: http://www.dntips.ir/post/2446 /// @@ -25,41 +18,46 @@ public IActionResult Index(int? id) var logBuilder = new StringBuilder(); var statusCodeReExecuteFeature = HttpContext.Features.Get(); - logBuilder.Append("Error ").Append(id).Append(" for ").Append(Request.Method).Append(' ') - .Append(statusCodeReExecuteFeature?.OriginalPath ?? Request.Path.Value).Append(Request.QueryString.Value) - .AppendLine("\n"); + + logBuilder.Append(value: "Error ") + .Append(id) + .Append(value: " for ") + .Append(Request.Method) + .Append(value: ' ') + .Append(statusCodeReExecuteFeature?.OriginalPath ?? Request.Path.Value) + .Append(Request.QueryString.Value) + .AppendLine(value: "\n"); var exceptionHandlerFeature = HttpContext.Features.Get(); + if (exceptionHandlerFeature?.Error != null) { var exception = exceptionHandlerFeature.Error; - logBuilder.Append("

Exception: ").Append(exception.Message).Append("

") + + logBuilder.Append(value: "

Exception: ") + .Append(exception.Message) + .Append(value: "

") .AppendLine(exception.StackTrace); } foreach (var header in Request.Headers) { var headerValues = header.Value.ToString(); - logBuilder.Append(header.Key).Append(": ").AppendLine(headerValues); + logBuilder.Append(header.Key).Append(value: ": ").AppendLine(headerValues); } - _logger.LogErrorMessage(logBuilder.ToString()); + logger.LogErrorMessage(logBuilder.ToString()); if (id == null) { - return View("Error"); + return View(viewName: "Error"); } - switch (id.Value) + return id.Value switch { - case 401: - case 403: - return View("AccessDenied"); - case 404: - return View("NotFound"); - - default: - return View("Error"); - } + 401 or 403 => View(viewName: "AccessDenied"), + 404 => View(viewName: "NotFound"), + _ => View(viewName: "Error") + }; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Controllers/HomeController.cs b/src/ASPNETCoreIdentitySample/Controllers/HomeController.cs index f1f738d..8bc8a48 100644 --- a/src/ASPNETCoreIdentitySample/Controllers/HomeController.cs +++ b/src/ASPNETCoreIdentitySample/Controllers/HomeController.cs @@ -9,16 +9,9 @@ namespace ASPNETCoreIdentitySample.Controllers; public class HomeController : Controller { [BreadCrumb(Title = "ایندکس", Order = 1)] - public IActionResult Index() - { - return View(); - } + public IActionResult Index() => View(); - [BreadCrumb(Title = "خطا", Order = 1)] - public IActionResult Error() - { - return View(); - } + [BreadCrumb(Title = "خطا", Order = 1)] public IActionResult Error() => View(); /// /// To test automatic challenge after redirecting from another site @@ -28,6 +21,15 @@ public IActionResult Error() public IActionResult CallBackResult(long token, string status, string orderId, string terminalNo, string rrn) { var userId = User.Identity?.GetUserId(); - return Json(new { userId, token, status, orderId, terminalNo, rrn }); + + return Json(new + { + userId, + token, + status, + orderId, + terminalNo, + rrn + }); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Program.cs b/src/ASPNETCoreIdentitySample/Program.cs index dcb0b7c..07bcd51 100644 --- a/src/ASPNETCoreIdentitySample/Program.cs +++ b/src/ASPNETCoreIdentitySample/Program.cs @@ -15,7 +15,7 @@ void ConfigureServices(IServiceCollection services, IConfiguration configuration, IWebHostEnvironment env) { - services.Configure(options => configuration.Bind(options)); + services.Configure(configuration.Bind); services.Configure(options => configuration.GetSection(key: "ContentSecurityPolicyConfig").Bind(options)); @@ -39,7 +39,7 @@ void ConfigureServices(IServiceCollection services, IConfiguration configuration services.AddCloudscribePagination(); services.AddWebOptimizerServices(); - services.AddControllersWithViews(options => { options.Filters.Add(typeof(ApplyCorrectYeKeFilterAttribute)); }); + services.AddControllersWithViews(options => { options.Filters.Add(); }); services.AddRazorPages(); }