From df4b62143d3cb457c28b2412f1aacb323339fbad Mon Sep 17 00:00:00 2001 From: Vahid Nasiri Date: Sat, 29 Jan 2022 18:11:40 +0330 Subject: [PATCH] Upgraded to .NET 6.0.101 --- .editorconfig | 231 ++ .github/workflows/build.yml | 2 +- .template.config/template.json | 28 +- .vscode/launch.json | 2 +- BannedSymbols.txt | 4 + Directory.Build.props | 94 + README.md | 10 +- common/AssemblyInfo.cs | 1 + global.json | 2 +- .../ASPNETCoreIdentitySample.Common.csproj | 11 +- ...tityFrameworkCoreModelBuilderExtensions.cs | 149 +- .../GuardToolkit/GuardExt.cs | 88 +- .../GuardToolkit/ImagesGuardExt.cs | 68 +- .../GuardToolkit/ValidationExtensions.cs | 45 +- .../IdentityToolkit/IdentityExtensions.cs | 82 +- .../PersianYeKeCommandInterceptor.cs | 139 +- .../PersianToolkit/UnicodeExtensions.cs | 72 +- .../WebToolkit/ServerInfo.cs | 31 +- ...tySample.DataLayer.InMemoryDatabase.csproj | 21 +- .../InMemoryDatabaseContext.cs | 9 +- ...moryDatabaseServiceCollectionExtensions.cs | 47 +- ...TCoreIdentitySample.DataLayer.MSSQL.csproj | 23 +- .../Migrations/.editorconfig | 2 + ...220129142821_V2022_01_29_1755.Designer.cs} | 76 +- ....cs => 20220129142821_V2022_01_29_1755.cs} | 4 +- .../Migrations/MsSqlDbContextModelSnapshot.cs | 72 +- .../MsSqlContextFactory.cs | 55 +- .../MsSqlDbContext.cs | 17 +- .../MsSqlServiceCollectionExtensions.cs | 118 +- .../_01-add_migrations.cmd | 2 +- .../_02-update_db.cmd | 2 +- ...CoreIdentitySample.DataLayer.SQLite.csproj | 21 +- .../Migrations/.editorconfig | 2 + ...220129143038_V2022_01_29_1758.Designer.cs} | 273 +- ....cs => 20220129143038_V2022_01_29_1758.cs} | 168 +- .../SQLiteDbContextModelSnapshot.cs | 269 +- .../SQLiteContextFactory.cs | 55 +- .../SQLiteDbContext.cs | 24 +- .../SQLiteServiceCollectionExtensions.cs | 125 +- .../_01-add_migrations.cmd | 2 +- .../_02-update_db.cmd | 2 +- .../ASPNETCoreIdentitySample.DataLayer.csproj | 25 +- .../AppDataProtectionKeyConfiguration.cs | 14 +- .../AppSqlCacheConfiguration.cs | 27 +- .../Configurations/CategoryConfiguration.cs | 14 +- .../Configurations/IdentityMappings.cs | 31 +- .../Configurations/ProductConfiguration.cs | 16 +- .../Configurations/RoleClaimConfiguration.cs | 20 +- .../Configurations/RoleConfiguration.cs | 12 +- .../Configurations/UserClaimConfiguration.cs | 20 +- .../Configurations/UserConfiguration.cs | 12 +- .../Configurations/UserLoginConfiguration.cs | 20 +- .../Configurations/UserRoleConfiguration.cs | 26 +- .../Configurations/UserTokenConfiguration.cs | 20 +- .../UserUsedPasswordConfiguration.cs | 24 +- .../Context/ApplicationDbContext.cs | 279 +-- .../Context/AuditableEntitiesInterceptor.cs | 80 + .../Context/IUnitOfWork.cs | 51 +- .../ASPNETCoreIdentitySample.Entities.csproj | 11 +- .../AuditableEntity/AppShadowProperties.cs | 9 + .../AuditableShadowProperties.cs | 373 +-- .../AuditableEntity/IAuditableEntity.cs | 19 +- .../Category.cs | 26 +- .../Identity/AppDataProtectionKey.cs | 13 +- .../Identity/AppLogItem.cs | 26 +- .../Identity/AppSqlCache.cs | 29 +- .../Identity/Role.cs | 48 +- .../Identity/RoleClaim.cs | 17 +- .../Identity/User.cs | 74 +- .../Identity/UserClaim.cs | 17 +- .../Identity/UserLogin.cs | 17 +- .../Identity/UserRole.cs | 19 +- .../Identity/UserToken.cs | 17 +- .../Identity/UserUsedPassword.cs | 15 +- .../Product.cs | 17 +- .../ASPNETCoreIdentitySample.IocConfig.csproj | 11 +- .../AddCustomServicesExtensions.cs | 73 +- .../AddDynamicPermissionsExtensions.cs | 31 +- .../AddIdentityOptionsExtensions.cs | 201 +- .../CustomDataProtectionExtensions.cs | 88 +- .../CustomTicketStoreExtensions.cs | 77 +- .../DbContextOptionsExtensions.cs | 79 +- .../IdentityServicesRegistry.cs | 46 +- .../ASPNETCoreIdentitySample.MsTests.csproj | 11 +- .../CoreTests.cs | 95 +- .../CustomNormalizerTests.cs | 41 +- .../TestHostingEnvironment.cs | 34 +- .../ASPNETCoreIdentitySample.Services.csproj | 11 +- .../Contracts/ICategoryService.cs | 14 +- .../Contracts/IProductService.cs | 14 +- .../Contracts/Identity/IAppLogItemsService.cs | 21 +- .../Identity/IApplicationRoleManager.cs | 475 ++-- .../Identity/IApplicationRoleStore.cs | 349 +-- .../Identity/IApplicationSignInManager.cs | 648 ++--- .../Identity/IApplicationUserManager.cs | 2187 +++++++++-------- .../Identity/IApplicationUserStore.cs | 566 +++-- .../Contracts/Identity/IEmailSender.cs | 19 +- .../Identity/IIdentityDbInitializer.cs | 30 +- .../Identity/ISecurityTrimmingService.cs | 11 +- .../Contracts/Identity/ISiteStatService.cs | 19 +- .../Contracts/Identity/ISmsSender.cs | 17 +- .../Identity/IUsedPasswordsService.cs | 19 +- .../Contracts/Identity/IUsersPhotoService.cs | 17 +- .../EfCategoryService.cs | 40 +- .../EfProductService.cs | 42 +- .../ApplicationClaimsPrincipalFactory.cs | 88 +- .../ApplicationClaimsTransformation.cs | 148 +- .../Identity/ApplicationRoleManager.cs | 378 ++- .../Identity/ApplicationRoleStore.cs | 68 +- .../Identity/ApplicationSignInManager.cs | 139 +- .../Identity/ApplicationUserManager.cs | 613 ++--- .../Identity/ApplicationUserStore.cs | 183 +- .../Identity/AuthMessageSender.cs | 83 +- ...EmailDataProtectionTokenProviderOptions.cs | 7 + .../ConfirmEmailDataProtectorTokenProvider.cs | 30 +- .../Identity/ConstantPolicies.cs | 11 +- .../Identity/ConstantRoles.cs | 9 +- .../Identity/CustomIdentityErrorDescriber.cs | 408 +-- .../Identity/CustomNormalizer.cs | 106 +- .../Identity/CustomPasswordValidator.cs | 204 +- .../Identity/CustomSecurityStampValidator.cs | 97 +- .../Identity/CustomUserValidator.cs | 154 +- .../Identity/DataProtectionKeyService.cs | 105 +- .../Identity/DistributedCacheTicketStore.cs | 89 +- .../Identity/DynamicPermissionRequirement.cs | 10 + .../DynamicPermissionsAuthorizationHandler.cs | 101 +- .../Identity/IdentityDbInitializer.cs | 258 +- .../Identity/Logger/AppLogItemsService.cs | 130 +- .../Identity/Logger/DbLogger.cs | 164 +- .../Logger/DbLoggerFactoryExtensions.cs | 14 +- .../Identity/Logger/DbLoggerProvider.cs | 199 +- .../Identity/Logger/LoggerItem.cs | 10 + .../Identity/MemoryCacheTicketStore.cs | 92 +- .../Identity/NoBrowserCacheMiddleware.cs | 33 +- .../NoBrowserCacheMiddlewareExtensions.cs | 11 + .../Identity/SecurityTrimmingService.cs | 88 +- .../Identity/SiteStatService.cs | 131 +- .../Identity/UsedPasswordsService.cs | 151 +- .../Identity/UsersPhotoService.cs | 110 +- ...ASPNETCoreIdentitySample.ViewModels.csproj | 9 +- .../Identity/AgeStatViewModel.cs | 21 +- .../Identity/ChangePasswordViewModel.cs | 45 +- .../Identity/ChangeUserPasswordViewModel.cs | 38 +- .../DynamicRoleClaimsManagerViewModel.cs | 18 +- .../ChangePasswordNotificationViewModel.cs | 9 +- .../Identity/Emails/EmailsBase.cs | 11 +- .../Identity/Emails/PasswordResetViewModel.cs | 11 +- .../RegisterEmailConfirmationViewModel.cs | 13 +- .../Emails/TwoFactorSendCodeViewModel.cs | 9 +- .../UserProfileUpdateNotificationViewModel.cs | 9 +- .../Identity/ForgotPasswordViewModel.cs | 15 +- .../Identity/LoginViewModel.cs | 25 +- .../Identity/ModelIdViewModel.cs | 9 +- .../Identity/OnlineUsersViewModel.cs | 18 +- .../Identity/PagedAppLogItemsViewModel.cs | 22 +- .../Identity/PagedUsersListViewModel.cs | 24 +- .../Identity/RegisterViewModel.cs | 82 +- .../Identity/ResetPasswordViewModel.cs | 44 +- .../Identity/RoleAndUsersCountViewModel.cs | 11 +- .../Identity/RoleViewModel.cs | 20 +- .../Identity/SearchUsersViewModel.cs | 73 +- .../Identity/Settings/ActiveDatabase.cs | 15 +- .../Identity/Settings/AdminUserSeed.cs | 15 +- .../Identity/Settings/Connectionstrings.cs | 13 +- .../Identity/Settings/CookieOptions.cs | 25 +- .../Settings/DataProtectionOptions.cs | 11 +- .../Settings/DataProtectionX509Certificate.cs | 11 +- .../DistributedSqlServerCacheOptions.cs | 13 +- .../Settings/DistributedSqliteCacheOptions.cs | 10 +- .../Identity/Settings/Localdb.cs | 11 +- .../Identity/Settings/Logging.cs | 11 +- .../Identity/Settings/Loglevel.cs | 13 +- .../Identity/Settings/SQLite.cs | 10 +- .../Identity/Settings/SiteSettings.cs | 50 +- .../Identity/Settings/SqlServer.cs | 9 +- .../Settings/UserAvatarImageOptions.cs | 11 +- .../Identity/SortOrder.cs | 13 +- .../Identity/TodayBirthDaysViewModel.cs | 14 +- .../Identity/UserCardItemActiveTab.cs | 7 + .../Identity/UserCardItemViewModel.cs | 24 +- .../Identity/UserProfileViewModel.cs | 96 +- .../Identity/VerifyCodeViewModel.cs | 25 +- .../Identity/ViewModelConstants.cs | 9 +- .../ASPNETCoreIdentitySample.csproj | 11 +- .../Areas/Identity/AreaConstants.cs | 15 +- .../Controllers/ChangePasswordController.cs | 165 +- .../ChangeUserPasswordController.cs | 197 +- .../DynamicPermissionsAreaSampleController.cs | 27 +- .../DynamicRoleClaimsManagerController.cs | 109 +- .../Controllers/ForgotPasswordController.cs | 220 +- .../Identity/Controllers/HomeController.cs | 14 +- .../Identity/Controllers/LoginController.cs | 186 +- .../Controllers/RegisterController.cs | 242 +- .../Controllers/RolesManagerController.cs | 273 +- .../Controllers/SystemLogController.cs | 104 +- .../Controllers/TwoFactorController.cs | 190 +- .../Controllers/UserCardController.cs | 102 +- .../Controllers/UserProfileController.cs | 469 ++-- .../Controllers/UsersManagerController.cs | 297 ++- .../TagHelpers/SecurityTrimmingTagHelper.cs | 103 +- .../TagHelpers/VisibilityTagHelper.cs | 50 +- .../OnlineUsersViewComponent.cs | 42 +- .../TodayBirthDaysViewComponent.cs | 40 +- .../Views/RolesManager/UsersInRole.cshtml | 3 +- .../DynamicPermissionsAreaSampleController.cs | 29 +- .../Areas/Test/TestAreaConstants.cs | 15 +- .../DynamicPermissionsSampleController.cs | 79 +- .../DynamicPermissionsTestController.cs | 87 +- .../Controllers/ErrorController.cs | 99 +- .../Controllers/HomeController.cs | 47 +- src/ASPNETCoreIdentitySample/Program.cs | 126 +- src/ASPNETCoreIdentitySample/Startup.cs | 85 - .../Views/Shared/_Layout.cshtml | 133 +- src/ASPNETCoreIdentitySample/appsettings.json | 14 +- .../bundleconfig.json | 72 +- .../wwwroot/App_Data/db.txt | 2 +- tag-it.bat | 2 +- 217 files changed, 9534 insertions(+), 8661 deletions(-) create mode 100644 .editorconfig create mode 100644 BannedSymbols.txt create mode 100644 Directory.Build.props create mode 100644 common/AssemblyInfo.cs create mode 100644 src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/.editorconfig rename src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/{20210731080434_V2021_07_31_1233.Designer.cs => 20220129142821_V2022_01_29_1755.Designer.cs} (92%) rename src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/{20210731080434_V2021_07_31_1233.cs => 20220129142821_V2022_01_29_1755.cs} (99%) create mode 100644 src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/.editorconfig rename src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/{20210731080521_V2021_07_31_1234.Designer.cs => 20220129143038_V2022_01_29_1758.Designer.cs} (73%) rename src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/{20210731080521_V2021_07_31_1234.cs => 20220129143038_V2022_01_29_1758.cs} (82%) create mode 100644 src/ASPNETCoreIdentitySample.DataLayer/Context/AuditableEntitiesInterceptor.cs create mode 100644 src/ASPNETCoreIdentitySample.Entities/AuditableEntity/AppShadowProperties.cs create mode 100644 src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectionTokenProviderOptions.cs create mode 100644 src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionRequirement.cs create mode 100644 src/ASPNETCoreIdentitySample.Services/Identity/Logger/LoggerItem.cs create mode 100644 src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddlewareExtensions.cs create mode 100644 src/ASPNETCoreIdentitySample.ViewModels/Identity/UserCardItemActiveTab.cs delete mode 100644 src/ASPNETCoreIdentitySample/Startup.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d6f2e6d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,231 @@ +[*.cs] + +roslynator_accessibility_modifiers = explicit +roslynator_enum_has_flag_style = operator +roslynator_object_creation_type_style = implicit_when_type_is_obvious + +# CA5395: Action method xyz needs to specify the HTTP request kind explicitly +dotnet_diagnostic.CA5395.severity = suggestion + +# CA5391: Method Index handles a HttpPost request without performing antiforgery token validation. You also need to ensure that your HTML form sends an antiforgery token. +dotnet_diagnostic.CA5391.severity = suggestion + +# CA1724: The type name Logging conflicts in whole or in part with the namespace name 'Microsoft.Extensions.Http.Logging'. Change either name to eliminate the conflict. +dotnet_diagnostic.CA1724.severity = suggestion + +# S125: Remove this commented out code. +dotnet_diagnostic.S125.severity = suggestion + +# Change 'List<>' to use 'Collection', 'ReadOnlyCollection' or 'KeyedCollection' +dotnet_diagnostic.CA1002.severity = suggestion + +# Do not call overridable members in constructor +dotnet_diagnostic.MA0056.severity = suggestion + +# CA1819: Properties should not return arrays +dotnet_diagnostic.CA1819.severity = suggestion + +# S1066: Merge this if statement with the enclosing one. +dotnet_diagnostic.S1066.severity = suggestion + +# Controller method is potentially vulnerable to Cross Site Request Forgery (CSRF). +dotnet_diagnostic.SCS0016.severity = suggestion + +# CA1716: Rename namespace `Shared` so that it no longer conflicts with the reserved language keyword 'Shared'. +dotnet_diagnostic.CA1716.severity = suggestion + +# S3903: Move into a named namespace. +dotnet_diagnostic.S3903.severity = suggestion + +# Modify to catch a more specific allowed exception type, or rethrow the exception csharp(CA1031) +dotnet_diagnostic.CA1031.severity = suggestion + +# Potential file path injection vulnerability was found csharp(CA3003) +dotnet_diagnostic.CA3003.severity = suggestion + +# Potential Path Traversal vulnerability was found where 'path' in csharp(SCS0018) +dotnet_diagnostic.SCS0018.severity = suggestion + +# Use an overload that has a IEqualityComparer or IComparer parameter csharp(MA0002) +dotnet_diagnostic.MA0002.severity = suggestion + +# Prefer return collection abstraction instead of implementation csharp(MA0016) +dotnet_diagnostic.MA0016.severity = suggestion + +# Change this to be read-only by removing the property setter csharp(CA2227) +dotnet_diagnostic.CA2227.severity = suggestion + +# Rename type name Permission so that it does not end in 'Permission' +dotnet_diagnostic.CA1711.severity = suggestion + +# CA1812: Program is an internal class that is apparently never instantiated. If so, remove the code from the assembly. If this class is intended to contain only static members, make it static (Shared in Visual Basic). +dotnet_diagnostic.CA1812.severity = suggestion + +# Declare types in namespaces +dotnet_diagnostic.MA0047.severity = suggestion + +# Make sure that this accessibility bypass is safe here. (S3011) +dotnet_diagnostic.S3011.severity = suggestion + +# Private classes or records which are not derived in the current assembly should be marked as 'sealed'. +dotnet_diagnostic.S3260.severity = suggestion + +# MA0026 : Complete the task +dotnet_diagnostic.MA0026.severity = suggestion + +# MA0023 : Add RegexOptions.ExplicitCapture to prevent capturing unneeded groups +dotnet_diagnostic.MA0023.severity = suggestion + +# MA0076: Do not use implicit culture-sensitive ToString in interpolated strings +dotnet_diagnostic.MA0076.severity = error + +# S101 : Rename class name to match pascal case naming rules +dotnet_diagnostic.S101.severity = suggestion + +# MA0007 : Add comma after the last value +dotnet_diagnostic.MA0007.severity = none + +# S1854: Remove this useless assignment to local variable +dotnet_diagnostic.S1854.severity = suggestion + +# MA0051 : Method is too long. maximum allowed: 60. +dotnet_diagnostic.MA0051.severity = suggestion + +# CA1308: In method 'urlToLower', replace the call to 'ToLowerInvariant' with 'ToUpperInvariant' (CA1308) +dotnet_diagnostic.CA1308.severity = suggestion + +# CA1040: Avoid empty interfaces +dotnet_diagnostic.CA1040.severity = suggestion + +# CA1829 Use the "Count" property instead of Enumerable.Count() +dotnet_diagnostic.CA1829.severity = suggestion + +# Use 'Count' property here instead. +dotnet_diagnostic.S2971.severity = suggestion + +# S1135 : Complete the task +dotnet_diagnostic.S1135.severity = suggestion + +# S2479: Replace the control character at position 7 by its escape sequence +dotnet_diagnostic.S2479.severity = suggestion + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = none + +# MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed +dotnet_diagnostic.MA0004.severity = none + +# CA1056: Change the type of property 'Url' from 'string' to 'System.Uri' +dotnet_diagnostic.CA1056.severity = suggestion + +# CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object +dotnet_diagnostic.CA1054.severity = suggestion + +# CA1055: Change the return type of method from 'string' to 'System.Uri' +dotnet_diagnostic.CA1055.severity = suggestion + +# S4457: Split this method into two, one handling parameters check and the other handling the asynchronous code. +dotnet_diagnostic.S4457.severity = none + +# AsyncFixer01: Unnecessary async/await usage +dotnet_diagnostic.AsyncFixer01.severity = suggestion + +# AsyncFixer02: Long-running or blocking operations inside an async method +dotnet_diagnostic.AsyncFixer02.severity = error + +# VSTHRD103: Call async methods when in an async method +dotnet_diagnostic.VSTHRD103.severity = error + +# AsyncFixer03: Fire & forget async void methods +dotnet_diagnostic.AsyncFixer03.severity = error + +# VSTHRD100: Avoid async void methods +dotnet_diagnostic.VSTHRD100.severity = error + +# VSTHRD101: Avoid unsupported async delegates +dotnet_diagnostic.VSTHRD101.severity = error + +# VSTHRD107: Await Task within using expression +dotnet_diagnostic.VSTHRD107.severity = error + +# AsyncFixer04: Fire & forget async call inside a using block +dotnet_diagnostic.AsyncFixer04.severity = error + +# VSTHRD110: Observe result of async calls +dotnet_diagnostic.VSTHRD110.severity = error + +# VSTHRD002: Avoid problematic synchronous waits +dotnet_diagnostic.VSTHRD002.severity = suggestion + +# MA0045: Do not use blocking call (make method async) +dotnet_diagnostic.MA0045.severity = suggestion + +# AsyncifyInvocation: Use Task Async +dotnet_diagnostic.AsyncifyInvocation.severity = error + +# AsyncifyVariable: Use Task Async +dotnet_diagnostic.AsyncifyVariable.severity = error + +# VSTHRD111: Use ConfigureAwait(bool) +dotnet_diagnostic.VSTHRD111.severity = none + +# MA0022: Return Task.FromResult instead of returning null +dotnet_diagnostic.MA0022.severity = error + +# VSTHRD114: Avoid returning a null Task +dotnet_diagnostic.VSTHRD114.severity = error + +# VSTHRD200: Use "Async" suffix for async methods +dotnet_diagnostic.VSTHRD200.severity = suggestion + +# MA0040: Specify a cancellation token +dotnet_diagnostic.MA0032.severity = suggestion + +# MA0040: Flow the cancellation token when available +dotnet_diagnostic.MA0040.severity = suggestion + +# MA0079: Use a cancellation token using .WithCancellation() +dotnet_diagnostic.MA0079.severity = suggestion + +# MA0080: Use a cancellation token using .WithCancellation() +dotnet_diagnostic.MA0080.severity = error + +#AsyncFixer05: Downcasting from a nested task to an outer task. +dotnet_diagnostic.AsyncFixer05.severity = error + +# ClrHeapAllocationAnalyzer ---------------------------------------------------- +# HAA0301: Closure Allocation Source +dotnet_diagnostic.HAA0301.severity = suggestion + +# HAA0601: Value type to reference type conversion causing boxing allocation +dotnet_diagnostic.HAA0601.severity = suggestion + +# HAA0302: Display class allocation to capture closure +dotnet_diagnostic.HAA0302.severity = suggestion + +# HAA0101: Array allocation for params parameter +dotnet_diagnostic.HAA0101.severity = suggestion + +# HAA0603: Delegate allocation from a method group +dotnet_diagnostic.HAA0603.severity = suggestion + +# HAA0602: Delegate on struct instance caused a boxing allocation +dotnet_diagnostic.HAA0602.severity = suggestion + +# HAA0401: Possible allocation of reference type enumerator +dotnet_diagnostic.HAA0401.severity = silent + +# HAA0303: Lambda or anonymous method in a generic method allocates a delegate instance +dotnet_diagnostic.HAA0303.severity = silent + +# HAA0102: Non-overridden virtual method call on value type +dotnet_diagnostic.HAA0102.severity = silent + +# HAA0502: Explicit new reference type allocation +dotnet_diagnostic.HAA0502.severity = none + +# HAA0505: Initializer reference type allocation +dotnet_diagnostic.HAA0505.severity = silent + +# force file scoped namespaces +csharp_style_namespace_declarations = file_scoped:warning \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b54a5c..b5dbd3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.302 + dotnet-version: 6.0.101 - name: npm install run: npm install - name: Build DNTIdentity diff --git a/.template.config/template.json b/.template.config/template.json index 1e6caab..98593e8 100644 --- a/.template.config/template.json +++ b/.template.config/template.json @@ -1,17 +1,17 @@ -{ +{ "$schema": "http://json.schemastore.org/template", -  "author": "VahidN ", -  "classifications": [ "MVC", ".NET Core", "ASP.NET Core" ],  -  "name": "Empty DNT.Identity project", -  "identity": "DNT.Identity", -  "shortName": "dntidentity", + "author": "VahidN ", + "classifications": ["MVC", ".NET Core", "ASP.NET Core"], + "name": "Empty DNT.Identity project", + "identity": "DNT.Identity", + "shortName": "dntidentity", "preferNameDirectory": true, -  "tags": { -    "language": "C#" -  }, -  "sourceName": "ASPNETCoreIdentitySample", - "symbols":{ - "Framework": { + "tags": { + "language": "C#" + }, + "sourceName": "ASPNETCoreIdentitySample", + "symbols": { + "Framework": { "type": "parameter", "description": "The target framework for the project.", "datatype": "choice", @@ -24,5 +24,5 @@ "defaultValue": "net5.0", "replaces": "net5.0" } - } -} \ No newline at end of file + } +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 97e052a..d9b10fb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceRoot}/src/ASPNETCoreIdentitySample/bin/Debug/net5.0/ASPNETCoreIdentitySample.dll", + "program": "${workspaceRoot}/src/ASPNETCoreIdentitySample/bin/Debug/net6.0/ASPNETCoreIdentitySample.dll", "args": [], "cwd": "${workspaceRoot}/src/ASPNETCoreIdentitySample", "stopAtEntry": false, diff --git a/BannedSymbols.txt b/BannedSymbols.txt new file mode 100644 index 0000000..002b1ac --- /dev/null +++ b/BannedSymbols.txt @@ -0,0 +1,4 @@ +# https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md +P:System.DateTime.Now;Use System.DateTime.UtcNow instead +P:System.DateTimeOffset.Now;Use System.DateTimeOffset.UtcNow instead +P:System.DateTimeOffset.DateTime;Use System.DateTimeOffset.UtcDateTime instead diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..974efa6 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,94 @@ + + + latest + 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 + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 090c076..637d69e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ 
-# سفارشی سازی ASP.NET Core Identity SDK-5.0.302 +# سفارشی سازی ASP.NET Core Identity SDK-6.0.101

@@ -14,10 +14,10 @@ ## برای اجرای این پروژه -- ابتدا نیاز است بر اساس شماره [SDK](https://dotnet.microsoft.com/download) ای که در عنوان پروژه مشاهده می‌کنید، نگارش متناظری را نصب کنید. به این معنا که این پروژه وابستگی خاصی به نگارش ویژه‌ای از Visual Studio ندارد. همینقدر که NET Core. را نصب کنید، می‌توانید آن‌را اجرا کنید. برای توسعه‌ی این برنامه از [VSCode](https://www.dotnettips.info/learningpaths/details/60) استفاده شده‌است. شماره نگارش SDK این پروژه توسط فایل [global.json‌](https://github.com/VahidN/DNTIdentity/blob/master/global.json#L3) قفل شده‌است تا اگر نگارش‌های دیگری را نصب کردید، با آن تداخل پیدا نکنند. +- ابتدا نیاز است بر اساس شماره [SDK](https://dotnet.microsoft.com/download) ای که در عنوان پروژه مشاهده می‌کنید، نگارش متناظری را نصب کنید. به این معنا که این پروژه وابستگی خاصی به نگارش ویژه‌ای از Visual Studio ندارد. همینقدر که NET Core. را نصب کنید، می‌توانید آن‌را اجرا کنید. برای توسعه‌ی این برنامه از [VSCode](https://www.dntips.ir/learningpaths/details/60) استفاده شده‌است. شماره نگارش SDK این پروژه توسط فایل [global.json‌](https://github.com/VahidN/DNTIdentity/blob/master/global.json#L3) قفل شده‌است تا اگر نگارش‌های دیگری را نصب کردید، با آن تداخل پیدا نکنند. - یک نکته: اگر قصد کار با [ویژوال استودیو 2017](https://github.com/dotnet/announcements/issues/108) را دارید، این برنامه دیگر از SDKهای جدید پشتیبانی نمی‌کند و حتما باید از نگارش 2019 آن استفاده کنید و یا می‌توانید شماره نگارش SDK را در فایل [global.json‌](https://github.com/VahidN/DNTIdentity/blob/master/global.json#L3) به شماره نگارشی که [ویژوال استودیو](https://github.com/dotnet/announcements/issues/108) شما پشتیبانی می‌کند تغییر داده و سپس کار ری‌استور پروژه را انجام دهید. - سپس آخرین نگارش [NodeJS](https://nodejs.org/en/download/current/) را نیز نصب کنید. از npm آن برای دریافت کتابخانه‌های سمت کلاینت پروژه که در فایل [package.json](https://github.com/VahidN/DNTIdentity/blob/master/src/ASPNETCoreIdentitySample/package.json) ذکر شده‌اند، استفاده می‌شود. -- بانک اطلاعاتی پیش‌فرض برنامه [LocalDB](https://www.dotnettips.info/post/2409) است که [از اینجا](https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SqlLocalDB.msi) قابل دریافت و نصب است (البته می‌توانید حالت InMemoryDatabase/SQLite را نیز در فایل [appsettings.json](https://github.com/VahidN/DNTIdentity/blob/master/src/ASPNETCoreIdentitySample/appsettings.json#L47) انتخاب کنید). +- بانک اطلاعاتی پیش‌فرض برنامه [LocalDB](https://www.dntips.ir/post/2409) است که [از اینجا](https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SqlLocalDB.msi) قابل دریافت و نصب است (البته می‌توانید حالت InMemoryDatabase/SQLite را نیز در فایل [appsettings.json](https://github.com/VahidN/DNTIdentity/blob/master/src/ASPNETCoreIdentitySample/appsettings.json#L47) انتخاب کنید). - سپس فایل [restore.bat](https://github.com/VahidN/DNTIdentity/blob/master/src/ASPNETCoreIdentitySample/_0-restore.bat) را اجرا کنید تا تمام وابستگی‌های سمت سرور و کلاینت پروژه، دریافت و نصب شوند. - در آخر فایل [dotnet_run.bat](https://github.com/VahidN/DNTIdentity/blob/master/src/ASPNETCoreIdentitySample/_1-dotnet_run.bat) را اجرا کنید، تا پروژه در آدرس https://localhost:5001 قابل دسترسی شود. مشخصات پیش‌فرض ورود به سیستم را در فایل [appsettings.json](https://github.com/VahidN/DNTIdentity/blob/master/src/ASPNETCoreIdentitySample/appsettings.json) می‌توانید مشاهده کنید. @@ -80,8 +80,8 @@ ## پیش از مشارکت -[« آشنایی با ساختار یک Pull Request خوب »](http://www.dotnettips.info/post/2033) +[« آشنایی با ساختار یک Pull Request خوب »](http://www.dntips.ir/post/2033) --- -جزئیات و توضیحات بیشتر این موارد را می‌توانید در گروه [ASP.NET Core Identity](http://www.dotnettips.info/search/label/asp.net%20core%20identity) پیگیری نمائید. +جزئیات و توضیحات بیشتر این موارد را می‌توانید در گروه [ASP.NET Core Identity](http://www.dntips.ir/search/label/asp.net%20core%20identity) پیگیری نمائید. diff --git a/common/AssemblyInfo.cs b/common/AssemblyInfo.cs new file mode 100644 index 0000000..4e11466 --- /dev/null +++ b/common/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: CLSCompliant(false)] diff --git a/global.json b/global.json index d58cbe0..5395ff4 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "5.0.302" + "version": "6.0.101" } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Common/ASPNETCoreIdentitySample.Common.csproj b/src/ASPNETCoreIdentitySample.Common/ASPNETCoreIdentitySample.Common.csproj index 994a42c..15b0051 100644 --- a/src/ASPNETCoreIdentitySample.Common/ASPNETCoreIdentitySample.Common.csproj +++ b/src/ASPNETCoreIdentitySample.Common/ASPNETCoreIdentitySample.Common.csproj @@ -1,13 +1,12 @@  - net5.0 - RCS1090 + net6.0 - - - - + + + + diff --git a/src/ASPNETCoreIdentitySample.Common/EFCoreToolkit/EntityFrameworkCoreModelBuilderExtensions.cs b/src/ASPNETCoreIdentitySample.Common/EFCoreToolkit/EntityFrameworkCoreModelBuilderExtensions.cs index 8429557..4f7830a 100644 --- a/src/ASPNETCoreIdentitySample.Common/EFCoreToolkit/EntityFrameworkCoreModelBuilderExtensions.cs +++ b/src/ASPNETCoreIdentitySample.Common/EFCoreToolkit/EntityFrameworkCoreModelBuilderExtensions.cs @@ -1,75 +1,114 @@ -using System; -using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -namespace ASPNETCoreIdentitySample.Common.EFCoreToolkit +namespace ASPNETCoreIdentitySample.Common.EFCoreToolkit; + +public static class EntityFrameworkCoreModelBuilderExtensions { - public static class EntityFrameworkCoreModelBuilderExtensions + public static void SetDecimalPrecision(this ModelBuilder builder) { - public static void SetDecimalPrecision(this ModelBuilder builder) + if (builder == null) { - foreach (var property in builder.Model.GetEntityTypes() - .SelectMany(t => t.GetProperties()) - .Where(p => p.ClrType == typeof(decimal) - || p.ClrType == typeof(decimal?))) - { - property.SetColumnType("decimal(18, 6)"); - } + throw new ArgumentNullException(nameof(builder)); + } + + foreach (var property in builder.Model.GetEntityTypes() + .SelectMany(t => t.GetProperties()) + .Where(p => p.ClrType == typeof(decimal) + || p.ClrType == typeof(decimal?))) + { + property.SetColumnType("decimal(18, 6)"); + } + } + + public static void SetCaseInsensitiveSearchesForSQLite(this ModelBuilder modelBuilder) + { + if (modelBuilder == null) + { + throw new ArgumentNullException(nameof(modelBuilder)); + } + + modelBuilder.UseCollation("NOCASE"); + + foreach (var property in modelBuilder.Model.GetEntityTypes() + .SelectMany(t => t.GetProperties()) + .Where(p => p.ClrType == typeof(string))) + { + property.SetCollation("NOCASE"); + } + } + + public static void AddDateTimeOffsetConverter(this ModelBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + // SQLite does not support DateTimeOffset + foreach (var property in builder.Model.GetEntityTypes() + .SelectMany(t => t.GetProperties()) + .Where(p => p.ClrType == typeof(DateTimeOffset))) + { + property.SetValueConverter( + new ValueConverter( + dateTimeOffset => dateTimeOffset.UtcDateTime, + dateTime => new DateTimeOffset(dateTime) + )); + } + + foreach (var property in builder.Model.GetEntityTypes() + .SelectMany(t => t.GetProperties()) + .Where(p => p.ClrType == typeof(DateTimeOffset?))) + { + property.SetValueConverter( + new ValueConverter( + dateTimeOffset => dateTimeOffset.Value.UtcDateTime, + dateTime => new DateTimeOffset(dateTime) + )); } + } - public static void AddDateTimeOffsetConverter(this ModelBuilder builder) + public static void AddDateTimeUtcKindConverter(this ModelBuilder builder) + { + if (builder == null) { - // SQLite does not support DateTimeOffset - foreach (var property in builder.Model.GetEntityTypes() - .SelectMany(t => t.GetProperties()) - .Where(p => p.ClrType == typeof(DateTimeOffset))) + throw new ArgumentNullException(nameof(builder)); + } + + // If you store a DateTime object to the DB with a DateTimeKind of either `Utc` or `Local`, + // when you read that record back from the DB you'll get a DateTime object whose kind is `Unspecified`. + // Here is a fix for it! + var dateTimeConverter = new ValueConverter( + v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(), + v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); + + var nullableDateTimeConverter = new ValueConverter( + v => !v.HasValue ? v : ToUniversalTime(v), + v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v); + + foreach (var property in builder.Model.GetEntityTypes() + .SelectMany(t => t.GetProperties())) + { + if (property.ClrType == typeof(DateTime)) { - property.SetValueConverter( - new ValueConverter( - convertToProviderExpression: dateTimeOffset => dateTimeOffset.UtcDateTime, - convertFromProviderExpression: dateTime => new DateTimeOffset(dateTime) - )); + property.SetValueConverter(dateTimeConverter); } - foreach (var property in builder.Model.GetEntityTypes() - .SelectMany(t => t.GetProperties()) - .Where(p => p.ClrType == typeof(DateTimeOffset?))) + if (property.ClrType == typeof(DateTime?)) { - property.SetValueConverter( - new ValueConverter( - convertToProviderExpression: dateTimeOffset => dateTimeOffset.Value.UtcDateTime, - convertFromProviderExpression: dateTime => new DateTimeOffset(dateTime) - )); + property.SetValueConverter(nullableDateTimeConverter); } } + } - public static void AddDateTimeUtcKindConverter(this ModelBuilder builder) + private static DateTime? ToUniversalTime(DateTime? dateTime) + { + if (!dateTime.HasValue) { - // If you store a DateTime object to the DB with a DateTimeKind of either `Utc` or `Local`, - // when you read that record back from the DB you'll get a DateTime object whose kind is `Unspecified`. - // Here is a fix for it! - var dateTimeConverter = new ValueConverter( - v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(), - v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); - - var nullableDateTimeConverter = new ValueConverter( - v => !v.HasValue ? v : (v.Value.Kind == DateTimeKind.Utc ? v : v.Value.ToUniversalTime()), - v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v); - - foreach (var property in builder.Model.GetEntityTypes() - .SelectMany(t => t.GetProperties())) - { - if (property.ClrType == typeof(DateTime)) - { - property.SetValueConverter(dateTimeConverter); - } - - if (property.ClrType == typeof(DateTime?)) - { - property.SetValueConverter(nullableDateTimeConverter); - } - } + return null; } + + return dateTime.Value.Kind == DateTimeKind.Utc ? dateTime : dateTime.Value.ToUniversalTime(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Common/GuardToolkit/GuardExt.cs b/src/ASPNETCoreIdentitySample.Common/GuardToolkit/GuardExt.cs index 90d5b31..26996a3 100644 --- a/src/ASPNETCoreIdentitySample.Common/GuardToolkit/GuardExt.cs +++ b/src/ASPNETCoreIdentitySample.Common/GuardToolkit/GuardExt.cs @@ -1,68 +1,50 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Globalization; -using System.Linq; -using DNTPersianUtils.Core; +using DNTPersianUtils.Core; -namespace ASPNETCoreIdentitySample.Common.GuardToolkit +namespace ASPNETCoreIdentitySample.Common.GuardToolkit; + +public static class GuardExt { - public static class GuardExt + public static bool ContainsNumber(this string inputText) { - ///

- /// Checks if the argument is null. - /// - public static void CheckArgumentIsNull(this object o, string name) - { - if (o == null) - throw new ArgumentNullException(name); - } - - /// - /// Checks if the parameter is null. - /// - public static void CheckMandatoryOption(this string s, string name) - { - if (string.IsNullOrEmpty(s)) - throw new ArgumentException(name); - } - - public static bool ContainsNumber(this string inputText) - { - return !string.IsNullOrWhiteSpace(inputText) && inputText.ToEnglishNumbers().Any(char.IsDigit); - } + return !string.IsNullOrWhiteSpace(inputText) && inputText.ToEnglishNumbers().Any(char.IsDigit); + } - public static bool HasConsecutiveChars(this string inputText, int sequenceLength = 3) + public static bool HasConsecutiveChars(this string inputText, int sequenceLength = 3) + { + var charEnumerator = StringInfo.GetTextElementEnumerator(inputText); + var currentElement = string.Empty; + var count = 1; + while (charEnumerator.MoveNext()) { - var charEnumerator = StringInfo.GetTextElementEnumerator(inputText); - var currentElement = string.Empty; - var count = 1; - while (charEnumerator.MoveNext()) + if (string.Equals(currentElement, charEnumerator.GetTextElement(), StringComparison.Ordinal)) { - if (currentElement == charEnumerator.GetTextElement()) - { - if (++count >= sequenceLength) - { - return true; - } - } - else + if (++count >= sequenceLength) { - count = 1; - currentElement = charEnumerator.GetTextElement(); + return true; } } - return false; + else + { + count = 1; + currentElement = charEnumerator.GetTextElement(); + } } - public static bool IsEmailAddress(this string inputText) - { - return !string.IsNullOrWhiteSpace(inputText) && new EmailAddressAttribute().IsValid(inputText); - } + return false; + } - public static bool IsNumeric(this string inputText) + public static bool IsEmailAddress(this string inputText) + { + return !string.IsNullOrWhiteSpace(inputText) && new EmailAddressAttribute().IsValid(inputText); + } + + public static bool IsNumeric(this string inputText) + { + if (string.IsNullOrWhiteSpace(inputText)) { - if (string.IsNullOrWhiteSpace(inputText)) return false; - return long.TryParse(inputText.ToEnglishNumbers(), out _); + return false; } + + return long.TryParse(inputText.ToEnglishNumbers(), NumberStyles.Number, CultureInfo.InvariantCulture, out _); } -} +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ImagesGuardExt.cs b/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ImagesGuardExt.cs index 5935f4c..82facd9 100644 --- a/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ImagesGuardExt.cs +++ b/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ImagesGuardExt.cs @@ -1,45 +1,63 @@ -using Microsoft.AspNetCore.Http; -using System.Drawing; -using System.IO; +using System.Drawing; +using System.Runtime.Versioning; +using Microsoft.AspNetCore.Http; -namespace ASPNETCoreIdentitySample.Common.GuardToolkit +namespace ASPNETCoreIdentitySample.Common.GuardToolkit; + +public static class ImagesGuardExt { - public static class ImagesGuardExt + [SupportedOSPlatform("windows")] + public static bool IsImageFile(this byte[] photoFile) { - public static bool IsImageFile(this byte[] photoFile) + if (photoFile == null || photoFile.Length == 0) { - if (photoFile == null || photoFile.Length == 0) - return false; + return false; + } - using (var memoryStream = new MemoryStream(photoFile)) + using (var memoryStream = new MemoryStream(photoFile)) + { + using (var img = Image.FromStream(memoryStream)) { - using (var img = Image.FromStream(memoryStream)) - { - return img.Width > 0; - } + return img.Width > 0; } } + } - public static bool IsValidImageFile(this IFormFile photoFile, int maxWidth = 150, int maxHeight = 150) + [SupportedOSPlatform("windows")] + public static bool IsImageFile(this IFormFile photoFile) + { + if (photoFile == null || photoFile.Length == 0) { - if (photoFile == null || photoFile.Length == 0) return false; - using (var img = Image.FromStream(photoFile.OpenReadStream())) - { - if (img.Width > maxWidth) return false; - if (img.Height > maxHeight) return false; - } - return true; + return false; + } + + using (var img = Image.FromStream(photoFile.OpenReadStream())) + { + return img.Width > 0; + } + } + + [SupportedOSPlatform("windows")] + public static bool IsValidImageFile(this IFormFile photoFile, int maxWidth = 150, int maxHeight = 150) + { + if (photoFile == null || photoFile.Length == 0) + { + return false; } - public static bool IsImageFile(this IFormFile photoFile) + using (var img = Image.FromStream(photoFile.OpenReadStream())) { - if (photoFile == null || photoFile.Length == 0) + if (img.Width > maxWidth) + { return false; + } - using (var img = Image.FromStream(photoFile.OpenReadStream())) + if (img.Height > maxHeight) { - return img.Width > 0; + return false; } } + + return true; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ValidationExtensions.cs b/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ValidationExtensions.cs index 5a41938..ba63450 100644 --- a/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ValidationExtensions.cs +++ b/src/ASPNETCoreIdentitySample.Common/GuardToolkit/ValidationExtensions.cs @@ -1,34 +1,35 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; +using System.Text; using Microsoft.EntityFrameworkCore; -namespace ASPNETCoreIdentitySample.Common.GuardToolkit +namespace ASPNETCoreIdentitySample.Common.GuardToolkit; + +public static class ValidationExtensions { - public static class ValidationExtensions + public static string GetValidationErrors(this DbContext context) { - public static string GetValidationErrors(this DbContext context) + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var errors = new StringBuilder(); + var entities = context.ChangeTracker.Entries() + .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified) + .Select(e => e.Entity); + foreach (var entity in entities) { - var errors = new StringBuilder(); - var entities = context.ChangeTracker.Entries() - .Where(e => e.State == EntityState.Added || e.State == 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)) { - var validationContext = new ValidationContext(entity); - var validationResults = new List(); - if (!Validator.TryValidateObject(entity, validationContext, validationResults, validateAllProperties: true)) + foreach (var validationResult in validationResults) { - foreach (var validationResult in validationResults) - { - var names = validationResult.MemberNames.Aggregate((s1, s2) => $"{s1}, {s2}"); - errors.AppendFormat("{0}: {1}", names, validationResult.ErrorMessage); - } + var names = validationResult.MemberNames.Aggregate((s1, s2) => $"{s1}, {s2}"); + errors.AppendFormat(CultureInfo.InvariantCulture, "{0}: {1}", names, validationResult.ErrorMessage); } } - - return errors.ToString(); } + + return errors.ToString(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Common/IdentityToolkit/IdentityExtensions.cs b/src/ASPNETCoreIdentitySample.Common/IdentityToolkit/IdentityExtensions.cs index 673cc83..e2f1085 100644 --- a/src/ASPNETCoreIdentitySample.Common/IdentityToolkit/IdentityExtensions.cs +++ b/src/ASPNETCoreIdentitySample.Common/IdentityToolkit/IdentityExtensions.cs @@ -1,54 +1,64 @@ -using System; -using System.Globalization; -using System.Security.Claims; -using System.Security.Principal; -using System.Text; +using System.Text; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.ModelBinding; -namespace ASPNETCoreIdentitySample.Common.IdentityToolkit +namespace ASPNETCoreIdentitySample.Common.IdentityToolkit; + +/// +/// More info: http://www.dntips.ir/post/2580 +/// And http://www.dntips.ir/post/2579 +/// +public static class IdentityExtensions { + public static void AddErrorsFromResult(this ModelStateDictionary modelStat, IdentityResult result) + { + if (modelStat == null) + { + throw new ArgumentNullException(nameof(modelStat)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + foreach (var error in result.Errors) + { + modelStat.AddModelError("", error.Description); + } + } + /// - /// More info: http://www.dotnettips.info/post/2580 - /// And http://www.dotnettips.info/post/2579 + /// IdentityResult errors list to string /// - public static class IdentityExtensions + public static string DumpErrors(this IdentityResult result, bool useHtmlNewLine = false) { - public static void AddErrorsFromResult(this ModelStateDictionary modelStat, IdentityResult result) + if (result == null) { - foreach (var error in result.Errors) - { - modelStat.AddModelError("", error.Description); - } + throw new ArgumentNullException(nameof(result)); } - /// - /// IdentityResult errors list to string - /// - public static string DumpErrors(this IdentityResult result, bool useHtmlNewLine = false) + var results = new StringBuilder(); + if (!result.Succeeded) { - var results = new StringBuilder(); - if (!result.Succeeded) + foreach (var errorDescription in result.Errors.Select(x => x.Description)) { - foreach (var error in result.Errors) + if (string.IsNullOrWhiteSpace(errorDescription)) + { + continue; + } + + if (!useHtmlNewLine) { - var errorDescription = error.Description; - if (string.IsNullOrWhiteSpace(errorDescription)) - { - continue; - } - - if (!useHtmlNewLine) - { - results.AppendLine(errorDescription); - } - else - { - results.Append(errorDescription).AppendLine("
"); - } + results.AppendLine(errorDescription); + } + else + { + results.Append(errorDescription).AppendLine("
"); } } - return results.ToString(); } + + return results.ToString(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Common/PersianToolkit/PersianYeKeCommandInterceptor.cs b/src/ASPNETCoreIdentitySample.Common/PersianToolkit/PersianYeKeCommandInterceptor.cs index ab7d6e3..c469998 100644 --- a/src/ASPNETCoreIdentitySample.Common/PersianToolkit/PersianYeKeCommandInterceptor.cs +++ b/src/ASPNETCoreIdentitySample.Common/PersianToolkit/PersianYeKeCommandInterceptor.cs @@ -1,93 +1,120 @@ -using System; using System.Data; using System.Data.Common; -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; using DNTPersianUtils.Core; using Microsoft.EntityFrameworkCore.Diagnostics; -namespace ASPNETCoreIdentitySample.Common.PersianToolkit +namespace ASPNETCoreIdentitySample.Common.PersianToolkit; + +public class PersianYeKeCommandInterceptor : DbCommandInterceptor { - public class PersianYeKeCommandInterceptor : DbCommandInterceptor + public override InterceptionResult ReaderExecuting( + DbCommand command, + CommandEventData eventData, + InterceptionResult result) { - public override InterceptionResult ReaderExecuting( - DbCommand command, - CommandEventData eventData, - InterceptionResult result) + if (command != null) { ApplyCorrectYeKe(command); - return result; } - public override ValueTask> ReaderExecutingAsync( - DbCommand command, - CommandEventData eventData, - InterceptionResult result, - CancellationToken cancellationToken = new CancellationToken()) + return result; + } + + public override ValueTask> ReaderExecutingAsync( + DbCommand command, + CommandEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = new()) + { + if (command != null) { ApplyCorrectYeKe(command); - return ValueTask.FromResult(result); } - public override InterceptionResult NonQueryExecuting( - DbCommand command, - CommandEventData eventData, - InterceptionResult result) + return ValueTask.FromResult(result); + } + + public override InterceptionResult NonQueryExecuting( + DbCommand command, + CommandEventData eventData, + InterceptionResult result) + { + if (command != null) { ApplyCorrectYeKe(command); - return result; } - public override ValueTask> NonQueryExecutingAsync( - DbCommand command, - CommandEventData eventData, - InterceptionResult result, - CancellationToken cancellationToken = new CancellationToken()) + return result; + } + + public override ValueTask> NonQueryExecutingAsync( + DbCommand command, + CommandEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = new()) + { + if (command != null) { ApplyCorrectYeKe(command); - return ValueTask.FromResult(result); } - public override InterceptionResult ScalarExecuting( - DbCommand command, - CommandEventData eventData, - InterceptionResult result) + return ValueTask.FromResult(result); + } + + public override InterceptionResult ScalarExecuting( + DbCommand command, + CommandEventData eventData, + InterceptionResult result) + { + if (command != null) { ApplyCorrectYeKe(command); - return result; } - public override ValueTask> ScalarExecutingAsync( - DbCommand command, - CommandEventData eventData, - InterceptionResult result, - CancellationToken cancellationToken = new CancellationToken()) + return result; + } + + public override ValueTask> ScalarExecutingAsync( + DbCommand command, + CommandEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = new()) + { + if (command != null) { ApplyCorrectYeKe(command); - return ValueTask.FromResult(result); } - private static void ApplyCorrectYeKe(DbCommand command) + return ValueTask.FromResult(result); + } + + [SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities", + Justification = "`ApplyCorrectYeKe()` method is safe.")] + private static void ApplyCorrectYeKe(DbCommand command) + { + if (command == null) { - command.CommandText = command.CommandText.ApplyCorrectYeKe(); + throw new ArgumentNullException(nameof(command)); + } + + command.CommandText = command.CommandText.ApplyCorrectYeKe(); - foreach (DbParameter parameter in command.Parameters) + foreach (DbParameter parameter in command.Parameters) + { + switch (parameter.DbType) { - switch (parameter.DbType) - { - case DbType.AnsiString: - case DbType.AnsiStringFixedLength: - case DbType.String: - case DbType.StringFixedLength: - case DbType.Xml: - if (!(parameter.Value is DBNull) && parameter.Value is string) - { - parameter.Value = - Convert.ToString(parameter.Value, CultureInfo.InvariantCulture).ApplyCorrectYeKe(); - } - break; - } + case DbType.AnsiString: + case DbType.AnsiStringFixedLength: + case DbType.String: + case DbType.StringFixedLength: + case DbType.Xml: + if (parameter.Value is not DBNull && parameter.Value is string) + { + parameter.Value = + Convert.ToString(parameter.Value, CultureInfo.InvariantCulture).ApplyCorrectYeKe(); + } + + break; } } } diff --git a/src/ASPNETCoreIdentitySample.Common/PersianToolkit/UnicodeExtensions.cs b/src/ASPNETCoreIdentitySample.Common/PersianToolkit/UnicodeExtensions.cs index 9a6669e..d94a4be 100644 --- a/src/ASPNETCoreIdentitySample.Common/PersianToolkit/UnicodeExtensions.cs +++ b/src/ASPNETCoreIdentitySample.Common/PersianToolkit/UnicodeExtensions.cs @@ -1,52 +1,52 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text; +using System.Text; -namespace ASPNETCoreIdentitySample.Common.PersianToolkit +namespace ASPNETCoreIdentitySample.Common.PersianToolkit; + +/// +/// More info: http://www.dntips.ir/post/2162 +/// +public static class UnicodeExtensions { - /// - /// More info: http://www.dotnettips.info/post/2162 - /// - public static class UnicodeExtensions + public static string RemoveDiacritics(this string text) { - public static string RemoveDiacritics(this string text) + if (string.IsNullOrWhiteSpace(text)) { - if (string.IsNullOrWhiteSpace(text)) - return string.Empty; + return string.Empty; + } - var normalizedString = text.Normalize(NormalizationForm.FormKC); - var stringBuilder = new StringBuilder(); + var normalizedString = text.Normalize(NormalizationForm.FormKC); + var stringBuilder = new StringBuilder(); - foreach (var c in normalizedString) + foreach (var c in normalizedString) + { + var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); + if (unicodeCategory != UnicodeCategory.NonSpacingMark) { - var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); - if (unicodeCategory != UnicodeCategory.NonSpacingMark) - { - stringBuilder.Append(c); - } + stringBuilder.Append(c); } - - return stringBuilder.ToString().Normalize(NormalizationForm.FormC); } - public static string CleanUnderLines(this string text) + return stringBuilder.ToString().Normalize(NormalizationForm.FormC); + } + + public static string CleanUnderLines(this string text) + { + if (string.IsNullOrWhiteSpace(text)) { - if (string.IsNullOrWhiteSpace(text)) - return string.Empty; + return string.Empty; + } - const char chr1600 = (char)1600; //ـ=1600 - const char chr8204 = (char)8204; //‌=8204 + const char chr1600 = (char)1600; //ـ=1600 + const char chr8204 = (char)8204; //‌=8204 - return text.Replace(chr1600.ToString(), "") - .Replace(chr8204.ToString(), ""); - } + return text.Replace(chr1600.ToString(), "", StringComparison.Ordinal) + .Replace(chr8204.ToString(), "", StringComparison.Ordinal); + } - public static string RemovePunctuation(this string text) - { - return string.IsNullOrWhiteSpace(text) ? - string.Empty : - new string(text.Where(c => !char.IsPunctuation(c)).ToArray()); - } + public static string RemovePunctuation(this string text) + { + return string.IsNullOrWhiteSpace(text) + ? string.Empty + : new string(text.Where(c => !char.IsPunctuation(c)).ToArray()); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Common/WebToolkit/ServerInfo.cs b/src/ASPNETCoreIdentitySample.Common/WebToolkit/ServerInfo.cs index 21301bf..c4020d3 100644 --- a/src/ASPNETCoreIdentitySample.Common/WebToolkit/ServerInfo.cs +++ b/src/ASPNETCoreIdentitySample.Common/WebToolkit/ServerInfo.cs @@ -1,26 +1,21 @@ -using System; -using System.IO; -using System.Linq; +namespace ASPNETCoreIdentitySample.Common.WebToolkit; -namespace ASPNETCoreIdentitySample.Common.WebToolkit +public static class ServerInfo { - public static class ServerInfo + public static string GetAppDataFolderPath() { - public static string GetAppDataFolderPath() + var appDataFolderPath = Path.Combine(GetWwwRootPath(), "App_Data"); + if (!Directory.Exists(appDataFolderPath)) { - var appDataFolderPath = Path.Combine(GetWwwRootPath(), "App_Data"); - if (!Directory.Exists(appDataFolderPath)) - { - Directory.CreateDirectory(appDataFolderPath); - } - return appDataFolderPath; + Directory.CreateDirectory(appDataFolderPath); } + return appDataFolderPath; + } - public static string GetWwwRootPath() - { - return Path.Combine( - AppContext.BaseDirectory.Split(new[] { "bin" }, StringSplitOptions.RemoveEmptyEntries).First(), - "wwwroot"); - } + public static string GetWwwRootPath() + { + return Path.Combine( + AppContext.BaseDirectory.Split(new[] { "bin" }, StringSplitOptions.RemoveEmptyEntries).First(), + "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 3a195d1..2247484 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase.csproj +++ b/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase.csproj @@ -1,26 +1,25 @@ - net5.0 - RCS1090 + net6.0 - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - + + + + + \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseContext.cs b/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseContext.cs index a7490da..3e3551d 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseContext.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseContext.cs @@ -1,12 +1,11 @@ using ASPNETCoreIdentitySample.DataLayer.Context; using Microsoft.EntityFrameworkCore; -namespace ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase +namespace ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase; + +public class InMemoryDatabaseContext : ApplicationDbContext { - public class InMemoryDatabaseContext : ApplicationDbContext + public InMemoryDatabaseContext(DbContextOptions options) : base(options) { - public InMemoryDatabaseContext(DbContextOptions options) : base(options) - { - } } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseServiceCollectionExtensions.cs b/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseServiceCollectionExtensions.cs index 72a07c5..91bef45 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseServiceCollectionExtensions.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase/InMemoryDatabaseServiceCollectionExtensions.cs @@ -1,33 +1,42 @@ -using System; -using ASPNETCoreIdentitySample.Common.PersianToolkit; +using ASPNETCoreIdentitySample.Common.PersianToolkit; using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -namespace ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase +namespace ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase; + +public static class InMemoryDatabaseServiceCollectionExtensions { - public static class InMemoryDatabaseServiceCollectionExtensions + public static IServiceCollection AddConfiguredInMemoryDbContext(this IServiceCollection services, + SiteSettings siteSettings) + { + services.AddScoped(serviceProvider => serviceProvider.GetRequiredService()); + services.AddDbContextPool( + (serviceProvider, optionsBuilder) => + optionsBuilder.UseConfiguredInMemoryDatabase(siteSettings, serviceProvider)); + return services; + } + + public static void UseConfiguredInMemoryDatabase( + this DbContextOptionsBuilder optionsBuilder, SiteSettings siteSettings, IServiceProvider serviceProvider) { - public static IServiceCollection AddConfiguredInMemoryDbContext(this IServiceCollection services, SiteSettings siteSettings) + if (optionsBuilder == null) { - services.AddScoped(serviceProvider => serviceProvider.GetRequiredService()); - services.AddEntityFrameworkInMemoryDatabase(); // It's added to access services from the dbcontext, remove it if you are using the normal `AddDbContext` and normal constructor dependency injection. - services.AddDbContextPool( - (serviceProvider, optionsBuilder) => optionsBuilder.UseConfiguredInMemoryDatabase(siteSettings, serviceProvider)); - return services; + throw new ArgumentNullException(nameof(optionsBuilder)); } - public static void UseConfiguredInMemoryDatabase( - this DbContextOptionsBuilder optionsBuilder, SiteSettings siteSettings, IServiceProvider serviceProvider) + if (siteSettings == null) { - optionsBuilder.UseInMemoryDatabase(siteSettings.ConnectionStrings.LocalDb.InitialCatalog); - optionsBuilder.UseInternalServiceProvider(serviceProvider); // It's added to access services from the dbcontext, remove it if you are using the normal `AddDbContext` and normal constructor dependency injection. - optionsBuilder.AddInterceptors(new PersianYeKeCommandInterceptor()); - optionsBuilder.ConfigureWarnings(warnings => - { - // ... - }); + throw new ArgumentNullException(nameof(siteSettings)); } + + optionsBuilder.UseInMemoryDatabase(siteSettings.ConnectionStrings.LocalDb.InitialCatalog); + optionsBuilder.AddInterceptors(new PersianYeKeCommandInterceptor(), + serviceProvider.GetRequiredService()); + optionsBuilder.ConfigureWarnings(warnings => + { + // ... + }); } } \ 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 511c254..6ebe9cf 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/ASPNETCoreIdentitySample.DataLayer.MSSQL.csproj +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/ASPNETCoreIdentitySample.DataLayer.MSSQL.csproj @@ -1,27 +1,26 @@ - net5.0 - RCS1090 + net6.0 - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - - + + + + + + \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/.editorconfig b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/.editorconfig new file mode 100644 index 0000000..c895abf --- /dev/null +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +generated_code = true diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20210731080434_V2021_07_31_1233.Designer.cs b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20220129142821_V2022_01_29_1755.Designer.cs similarity index 92% rename from src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20210731080434_V2021_07_31_1233.Designer.cs rename to src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20220129142821_V2022_01_29_1755.Designer.cs index 5d72186..9269b2a 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20210731080434_V2021_07_31_1233.Designer.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20220129142821_V2022_01_29_1755.Designer.cs @@ -7,26 +7,30 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +#nullable disable + namespace ASPNETCoreIdentitySample.DataLayer.MSSQL.Migrations { [DbContext(typeof(MsSqlDbContext))] - [Migration("20210731080434_V2021_07_31_1233")] - partial class V2021_07_31_1233 + [Migration("20220129142821_V2022_01_29_1755")] + partial class V2022_01_29_1755 { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.8") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasAnnotation("ProductVersion", "6.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Category", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("CreatedByBrowserName") .HasMaxLength(1000) @@ -74,8 +78,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("FriendlyName") .HasColumnType("nvarchar(450)"); @@ -89,15 +94,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsUnique() .HasFilter("[FriendlyName] IS NOT NULL"); - b.ToTable("AppDataProtectionKeys"); + b.ToTable("AppDataProtectionKeys", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.AppLogItem", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("CreatedByBrowserName") .HasMaxLength(1000) @@ -181,8 +187,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("ConcurrencyStamp") .IsConcurrencyToken() @@ -234,15 +241,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasDatabaseName("RoleNameIndex") .HasFilter("[NormalizedName] IS NOT NULL"); - b.ToTable("AppRoles"); + b.ToTable("AppRoles", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.RoleClaim", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); @@ -285,15 +293,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AppRoleClaims"); + b.ToTable("AppRoleClaims", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.User", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("AccessFailedCount") .HasColumnType("int"); @@ -407,15 +416,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasDatabaseName("UserNameIndex") .HasFilter("[NormalizedUserName] IS NOT NULL"); - b.ToTable("AppUsers"); + b.ToTable("AppUsers", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserClaim", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); @@ -458,7 +468,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserClaims"); + b.ToTable("AppUserClaims", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserLogin", b => @@ -507,7 +517,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserLogins"); + b.ToTable("AppUserLogins", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserRole", b => @@ -550,7 +560,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AppUserRoles"); + b.ToTable("AppUserRoles", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserToken", b => @@ -597,15 +607,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AppUserTokens"); + b.ToTable("AppUserTokens", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserUsedPassword", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("CreatedByBrowserName") .HasMaxLength(1000) @@ -647,15 +658,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserUsedPasswords"); + b.ToTable("AppUserUsedPasswords", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Product", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("CategoryId") .HasColumnType("int"); diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20210731080434_V2021_07_31_1233.cs b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20220129142821_V2022_01_29_1755.cs similarity index 99% rename from src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20210731080434_V2021_07_31_1233.cs rename to src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20220129142821_V2022_01_29_1755.cs index 947ceee..b4653fa 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20210731080434_V2021_07_31_1233.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/20220129142821_V2022_01_29_1755.cs @@ -1,9 +1,11 @@ using System; using Microsoft.EntityFrameworkCore.Migrations; +#nullable disable + namespace ASPNETCoreIdentitySample.DataLayer.MSSQL.Migrations { - public partial class V2021_07_31_1233 : Migration + public partial class V2022_01_29_1755 : Migration { protected override void Up(MigrationBuilder migrationBuilder) { diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/MsSqlDbContextModelSnapshot.cs b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/MsSqlDbContextModelSnapshot.cs index bde7043..ce7b93a 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/MsSqlDbContextModelSnapshot.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/Migrations/MsSqlDbContextModelSnapshot.cs @@ -6,6 +6,8 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +#nullable disable + namespace ASPNETCoreIdentitySample.DataLayer.MSSQL.Migrations { [DbContext(typeof(MsSqlDbContext))] @@ -15,16 +17,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.8") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasAnnotation("ProductVersion", "6.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Category", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("CreatedByBrowserName") .HasMaxLength(1000) @@ -72,8 +76,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("FriendlyName") .HasColumnType("nvarchar(450)"); @@ -87,15 +92,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsUnique() .HasFilter("[FriendlyName] IS NOT NULL"); - b.ToTable("AppDataProtectionKeys"); + b.ToTable("AppDataProtectionKeys", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.AppLogItem", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("CreatedByBrowserName") .HasMaxLength(1000) @@ -179,8 +185,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("ConcurrencyStamp") .IsConcurrencyToken() @@ -232,15 +239,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasDatabaseName("RoleNameIndex") .HasFilter("[NormalizedName] IS NOT NULL"); - b.ToTable("AppRoles"); + b.ToTable("AppRoles", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.RoleClaim", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); @@ -283,15 +291,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AppRoleClaims"); + b.ToTable("AppRoleClaims", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.User", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("AccessFailedCount") .HasColumnType("int"); @@ -405,15 +414,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasDatabaseName("UserNameIndex") .HasFilter("[NormalizedUserName] IS NOT NULL"); - b.ToTable("AppUsers"); + b.ToTable("AppUsers", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserClaim", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); @@ -456,7 +466,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserClaims"); + b.ToTable("AppUserClaims", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserLogin", b => @@ -505,7 +515,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserLogins"); + b.ToTable("AppUserLogins", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserRole", b => @@ -548,7 +558,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AppUserRoles"); + b.ToTable("AppUserRoles", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserToken", b => @@ -595,15 +605,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AppUserTokens"); + b.ToTable("AppUserTokens", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserUsedPassword", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("CreatedByBrowserName") .HasMaxLength(1000) @@ -645,15 +656,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserUsedPasswords"); + b.ToTable("AppUserUsedPasswords", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Product", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); b.Property("CategoryId") .HasColumnType("int"); diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlContextFactory.cs b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlContextFactory.cs index ac56cbc..cae6932 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlContextFactory.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlContextFactory.cs @@ -1,43 +1,42 @@ -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using ASPNETCoreIdentitySample.DataLayer.Context; -using System; -using System.IO; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace ASPNETCoreIdentitySample.DataLayer.MSSQL +namespace ASPNETCoreIdentitySample.DataLayer.MSSQL; + +public class MsSqlContextFactory : IDesignTimeDbContextFactory { - public class MsSqlContextFactory : IDesignTimeDbContextFactory + public MsSqlDbContext CreateDbContext(string[] args) { - public MsSqlDbContext CreateDbContext(string[] args) - { - var services = new ServiceCollection(); - services.AddOptions(); - services.AddSingleton(); - services.AddSingleton(); + var services = new ServiceCollection(); + services.AddOptions(); + services.AddLogging(cfg => cfg.AddConsole().AddDebug()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - var basePath = Directory.GetCurrentDirectory(); - Console.WriteLine($"Using `{basePath}` as the ContentRootPath"); - var configuration = new ConfigurationBuilder() - .SetBasePath(basePath) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .Build(); - services.AddSingleton(provider => configuration); - services.Configure(options => configuration.Bind(options)); + var basePath = Directory.GetCurrentDirectory(); + WriteLine($"Using `{basePath}` as the ContentRootPath"); + var configuration = new ConfigurationBuilder() + .SetBasePath(basePath) + .AddJsonFile("appsettings.json", false, true) + .Build(); + services.AddSingleton(provider => configuration); + services.Configure(options => configuration.Bind(options)); - var siteSettings = services.BuildServiceProvider().GetRequiredService>(); - siteSettings.Value.ActiveDatabase = ActiveDatabase.LocalDb; + var serviceProvider = services.BuildServiceProvider(); + var siteSettings = serviceProvider.GetRequiredService>(); + siteSettings.Value.ActiveDatabase = ActiveDatabase.LocalDb; - services.AddEntityFrameworkSqlServer(); // It's added to access services from the dbcontext, remove it if you are using the normal `AddDbContext` and normal constructor dependency injection. - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseConfiguredMsSql(siteSettings.Value, services.BuildServiceProvider()); + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseConfiguredMsSql(siteSettings.Value, serviceProvider); - return new MsSqlDbContext(optionsBuilder.Options); - } + return new MsSqlDbContext(optionsBuilder.Options); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlDbContext.cs b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlDbContext.cs index fc2a33d..b88410b 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlDbContext.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlDbContext.cs @@ -1,12 +1,23 @@ using ASPNETCoreIdentitySample.DataLayer.Context; using Microsoft.EntityFrameworkCore; -namespace ASPNETCoreIdentitySample.DataLayer.MSSQL +namespace ASPNETCoreIdentitySample.DataLayer.MSSQL; + +public class MsSqlDbContext : ApplicationDbContext { - public class MsSqlDbContext : ApplicationDbContext + public MsSqlDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) { - public MsSqlDbContext(DbContextOptions options) : base(options) + if (builder == null) { + throw new ArgumentNullException(nameof(builder)); } + + base.OnModelCreating(builder); + + // NOTE: Add custom MSSQL's settings here ... } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlServiceCollectionExtensions.cs b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlServiceCollectionExtensions.cs index 0a56d05..a346639 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlServiceCollectionExtensions.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/MsSqlServiceCollectionExtensions.cs @@ -1,66 +1,98 @@ -using System; -using System.IO; using ASPNETCoreIdentitySample.Common.PersianToolkit; using ASPNETCoreIdentitySample.Common.WebToolkit; using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; -namespace ASPNETCoreIdentitySample.DataLayer.MSSQL +namespace ASPNETCoreIdentitySample.DataLayer.MSSQL; + +public static class MsSqlServiceCollectionExtensions { - public static class MsSqlServiceCollectionExtensions + public static IServiceCollection AddConfiguredMsSqlDbContext(this IServiceCollection services, + SiteSettings siteSettings) { - public static IServiceCollection AddConfiguredMsSqlDbContext(this IServiceCollection services, SiteSettings siteSettings) + services.AddScoped(serviceProvider => { - services.AddScoped(serviceProvider => serviceProvider.GetRequiredService()); - services.AddEntityFrameworkSqlServer(); // It's added to access services from the dbcontext, remove it if you are using the normal `AddDbContext` and normal constructor dependency injection. - services.AddDbContextPool( - (serviceProvider, optionsBuilder) => optionsBuilder.UseConfiguredMsSql(siteSettings, serviceProvider)); - return services; + var context = serviceProvider.GetRequiredService(); + SetCascadeOnSaveChanges(context); + return context; + }); + services.AddDbContextPool( + (serviceProvider, optionsBuilder) => optionsBuilder.UseConfiguredMsSql(siteSettings, serviceProvider)); + return services; + } + + 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 UseConfiguredMsSql( + this DbContextOptionsBuilder optionsBuilder, SiteSettings siteSettings, IServiceProvider serviceProvider) + { + if (optionsBuilder == null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); } - public static void UseConfiguredMsSql( - this DbContextOptionsBuilder optionsBuilder, SiteSettings siteSettings, IServiceProvider serviceProvider) + if (siteSettings == null) { - var connectionString = siteSettings.GetMsSqlDbConnectionString(); - optionsBuilder.UseSqlServer( - connectionString, - sqlServerOptionsBuilder => - { - sqlServerOptionsBuilder.CommandTimeout((int)TimeSpan.FromMinutes(3).TotalSeconds); - sqlServerOptionsBuilder.EnableRetryOnFailure(); - sqlServerOptionsBuilder.MigrationsAssembly(typeof(MsSqlServiceCollectionExtensions).Assembly.FullName); - }); - optionsBuilder.UseInternalServiceProvider(serviceProvider); // It's added to access services from the dbcontext, remove it if you are using the normal `AddDbContext` and normal constructor dependency injection. - optionsBuilder.AddInterceptors(new PersianYeKeCommandInterceptor()); - optionsBuilder.ConfigureWarnings(warnings => + throw new ArgumentNullException(nameof(siteSettings)); + } + + var connectionString = siteSettings.GetMsSqlDbConnectionString(); + optionsBuilder.UseSqlServer( + connectionString, + sqlServerOptionsBuilder => { - // ... + sqlServerOptionsBuilder.CommandTimeout((int)TimeSpan.FromMinutes(3).TotalSeconds); + sqlServerOptionsBuilder.EnableRetryOnFailure(10, + TimeSpan.FromSeconds(7), + null); + sqlServerOptionsBuilder.MigrationsAssembly(typeof(MsSqlServiceCollectionExtensions).Assembly.FullName); }); - } + optionsBuilder.AddInterceptors(new PersianYeKeCommandInterceptor(), + serviceProvider.GetRequiredService()); + optionsBuilder.ConfigureWarnings(warnings => + { + warnings.Log( + (CoreEventId.LazyLoadOnDisposedContextWarning, LogLevel.Warning), + (CoreEventId.DetachedLazyLoadingWarning, LogLevel.Warning), + (CoreEventId.ManyServiceProvidersCreatedWarning, LogLevel.Warning), + (CoreEventId.SensitiveDataLoggingEnabledWarning, LogLevel.Information) + ); + }); + optionsBuilder.EnableSensitiveDataLogging().EnableDetailedErrors(); + } - public static string GetMsSqlDbConnectionString(this SiteSettings siteSettingsValue) + public static string GetMsSqlDbConnectionString(this SiteSettings siteSettingsValue) + { + if (siteSettingsValue == null) { - if (siteSettingsValue == null) - { - throw new ArgumentNullException(nameof(siteSettingsValue)); - } + throw new ArgumentNullException(nameof(siteSettingsValue)); + } - switch (siteSettingsValue.ActiveDatabase) - { - case ActiveDatabase.LocalDb: - var attachDbFilename = siteSettingsValue.ConnectionStrings.LocalDb.AttachDbFilename; - var attachDbFilenamePath = Path.Combine(ServerInfo.GetAppDataFolderPath(), attachDbFilename); - var localDbInitialCatalog = siteSettingsValue.ConnectionStrings.LocalDb.InitialCatalog; - return $@"Data Source=(LocalDB)\MSSQLLocalDB;Initial Catalog={localDbInitialCatalog};AttachDbFilename={attachDbFilenamePath};Integrated Security=True;MultipleActiveResultSets=True;"; + switch (siteSettingsValue.ActiveDatabase) + { + case ActiveDatabase.LocalDb: + var attachDbFilename = siteSettingsValue.ConnectionStrings.LocalDb.AttachDbFilename; + var attachDbFilenamePath = Path.Combine(ServerInfo.GetAppDataFolderPath(), attachDbFilename); + var localDbInitialCatalog = siteSettingsValue.ConnectionStrings.LocalDb.InitialCatalog; + return + $@"Data Source=(LocalDB)\MSSQLLocalDB;Initial Catalog={localDbInitialCatalog};AttachDbFilename={attachDbFilenamePath};Integrated Security=True;MultipleActiveResultSets=True;"; - case ActiveDatabase.SqlServer: - return siteSettingsValue.ConnectionStrings.SqlServer.ApplicationDbContextConnection; + case ActiveDatabase.SqlServer: + return siteSettingsValue.ConnectionStrings.SqlServer.ApplicationDbContextConnection; - default: - throw new NotSupportedException("Please set the ActiveDatabase in appsettings.json file to `LocalDb` or `SqlServer`."); - } + default: + throw new NotSupportedException( + "Please set the ActiveDatabase in appsettings.json file to `LocalDb` or `SqlServer`."); } } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_01-add_migrations.cmd b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_01-add_migrations.cmd index c429938..de0c3d3 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 5.0.8 +dotnet tool update --global dotnet-ef --version 6.0.1 dotnet build dotnet ef migrations --startup-project ../ASPNETCoreIdentitySample/ add V%mydate%_%mytime% --context MsSqlDbContext pause \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_02-update_db.cmd b/src/ASPNETCoreIdentitySample.DataLayer.MSSQL/_02-update_db.cmd index 21f5c80..02be86a 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 5.0.8 +dotnet tool update --global dotnet-ef --version 6.0.1 dotnet build dotnet ef --startup-project ../ASPNETCoreIdentitySample/ database update --context MsSqlDbContext pause \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/ASPNETCoreIdentitySample.DataLayer.SQLite.csproj b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/ASPNETCoreIdentitySample.DataLayer.SQLite.csproj index 95502db..e0b1b98 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/ASPNETCoreIdentitySample.DataLayer.SQLite.csproj +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/ASPNETCoreIdentitySample.DataLayer.SQLite.csproj @@ -1,26 +1,25 @@ - net5.0 - RCS1090 + net6.0 - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - - + + + + + + \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/.editorconfig b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/.editorconfig new file mode 100644 index 0000000..c895abf --- /dev/null +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +generated_code = true diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20210731080521_V2021_07_31_1234.Designer.cs b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20220129143038_V2022_01_29_1758.Designer.cs similarity index 73% rename from src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20210731080521_V2021_07_31_1234.Designer.cs rename to src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20220129143038_V2022_01_29_1758.Designer.cs index e147dec..28a077d 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20210731080521_V2021_07_31_1234.Designer.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20220129143038_V2022_01_29_1758.Designer.cs @@ -6,17 +6,20 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +#nullable disable + namespace ASPNETCoreIdentitySample.DataLayer.SQLite.Migrations { [DbContext(typeof(SQLiteDbContext))] - [Migration("20210731080521_V2021_07_31_1234")] - partial class V2021_07_31_1234 + [Migration("20220129143038_V2022_01_29_1758")] + partial class V2022_01_29_1758 { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "5.0.8"); + .UseCollation("NOCASE") + .HasAnnotation("ProductVersion", "6.0.1"); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Category", b => { @@ -26,11 +29,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -40,11 +45,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -55,11 +62,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Title") .IsRequired() - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); @@ -73,17 +82,19 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("FriendlyName") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("XmlData") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); b.HasIndex("FriendlyName") .IsUnique(); - b.ToTable("AppDataProtectionKeys"); + b.ToTable("AppDataProtectionKeys", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.AppLogItem", b => @@ -94,11 +105,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -110,21 +123,26 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("LogLevel") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Logger") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Message") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -133,10 +151,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("StateJson") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Url") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); @@ -147,7 +167,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.Property("Id") .HasMaxLength(449) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("AbsoluteExpiration") .HasColumnType("TEXT"); @@ -178,15 +199,18 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -195,15 +219,18 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("Description") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -213,11 +240,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Name") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("NormalizedName") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); @@ -225,7 +254,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsUnique() .HasDatabaseName("RoleNameIndex"); - b.ToTable("AppRoles"); + b.ToTable("AppRoles", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.RoleClaim", b => @@ -235,18 +264,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("ClaimType") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ClaimValue") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -256,11 +289,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -275,7 +310,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AppRoleClaims"); + b.ToTable("AppRoleClaims", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.User", b => @@ -292,15 +327,18 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -310,14 +348,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Email") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("EmailConfirmed") .HasColumnType("INTEGER"); b.Property("FirstName") .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("IsActive") .HasColumnType("INTEGER"); @@ -327,13 +367,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("LastName") .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("LastVisitDateTime") .HasColumnType("TEXT"); b.Property("Location") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("LockoutEnabled") .HasColumnType("INTEGER"); @@ -343,11 +385,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -357,34 +401,41 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("NormalizedEmail") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("NormalizedUserName") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("PasswordHash") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("PhoneNumber") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("PhoneNumberConfirmed") .HasColumnType("INTEGER"); b.Property("PhotoFileName") .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("SecurityStamp") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("TwoFactorEnabled") .HasColumnType("INTEGER"); b.Property("UserName") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); @@ -395,7 +446,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsUnique() .HasDatabaseName("UserNameIndex"); - b.ToTable("AppUsers"); + b.ToTable("AppUsers", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserClaim", b => @@ -405,18 +456,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("ClaimType") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ClaimValue") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -426,11 +481,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -445,24 +502,28 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserClaims"); + b.ToTable("AppUserClaims", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserLogin", b => { b.Property("LoginProvider") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ProviderKey") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -472,11 +533,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -485,7 +548,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("ProviderDisplayName") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("UserId") .HasColumnType("INTEGER"); @@ -494,7 +558,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserLogins"); + b.ToTable("AppUserLogins", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserRole", b => @@ -507,11 +571,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -521,11 +587,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -537,7 +605,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AppUserRoles"); + b.ToTable("AppUserRoles", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserToken", b => @@ -546,18 +614,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("LoginProvider") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Name") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -567,11 +639,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -580,11 +654,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("Value") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AppUserTokens"); + b.ToTable("AppUserTokens", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserUsedPassword", b => @@ -595,11 +670,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -610,15 +687,18 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("HashedPassword") .IsRequired() .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -633,7 +713,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserUsedPasswords"); + b.ToTable("AppUserUsedPasswords", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Product", b => @@ -647,11 +727,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -661,11 +743,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -676,7 +760,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Price") .HasColumnType("decimal(18, 6)"); diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20210731080521_V2021_07_31_1234.cs b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20220129143038_V2022_01_29_1758.cs similarity index 82% rename from src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20210731080521_V2021_07_31_1234.cs rename to src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20220129143038_V2022_01_29_1758.cs index c20c5db..fd4b20e 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20210731080521_V2021_07_31_1234.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/20220129143038_V2022_01_29_1758.cs @@ -1,9 +1,11 @@ using System; using Microsoft.EntityFrameworkCore.Migrations; +#nullable disable + namespace ASPNETCoreIdentitySample.DataLayer.SQLite.Migrations { - public partial class V2021_07_31_1234 : Migration + public partial class V2022_01_29_1758 : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -16,8 +18,8 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - FriendlyName = table.Column(type: "TEXT", nullable: true), - XmlData = table.Column(type: "TEXT", nullable: true) + FriendlyName = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + XmlData = table.Column(type: "TEXT", nullable: true, collation: "NOCASE") }, constraints: table => { @@ -32,16 +34,16 @@ protected override void Up(MigrationBuilder migrationBuilder) .Annotation("Sqlite:Autoincrement", true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), EventId = table.Column(type: "INTEGER", nullable: false), - Url = table.Column(type: "TEXT", nullable: true), - LogLevel = table.Column(type: "TEXT", nullable: true), - Logger = table.Column(type: "TEXT", nullable: true), - Message = table.Column(type: "TEXT", nullable: true), - StateJson = table.Column(type: "TEXT", nullable: true), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + Url = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + LogLevel = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + Logger = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + Message = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + StateJson = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true) }, @@ -56,18 +58,18 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - Description = table.Column(type: "TEXT", nullable: true), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + Description = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true), - Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true, collation: "NOCASE"), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true, collation: "NOCASE"), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true, collation: "NOCASE") }, constraints: table => { @@ -79,7 +81,7 @@ protected override void Up(MigrationBuilder migrationBuilder) schema: "dbo", columns: table => new { - Id = table.Column(type: "TEXT", maxLength: 449, nullable: false), + Id = table.Column(type: "TEXT", maxLength: 449, nullable: false, collation: "NOCASE"), Value = table.Column(type: "BLOB", nullable: false), ExpiresAtTime = table.Column(type: "TEXT", nullable: false), SlidingExpirationInSeconds = table.Column(type: "INTEGER", nullable: true), @@ -96,31 +98,31 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - FirstName = table.Column(type: "TEXT", maxLength: 450, nullable: true), - LastName = table.Column(type: "TEXT", maxLength: 450, nullable: true), - PhotoFileName = table.Column(type: "TEXT", maxLength: 450, nullable: true), + FirstName = table.Column(type: "TEXT", maxLength: 450, nullable: true, collation: "NOCASE"), + LastName = table.Column(type: "TEXT", maxLength: 450, nullable: true, collation: "NOCASE"), + PhotoFileName = table.Column(type: "TEXT", maxLength: 450, nullable: true, collation: "NOCASE"), BirthDate = table.Column(type: "TEXT", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), LastVisitDateTime = table.Column(type: "TEXT", nullable: true), IsEmailPublic = table.Column(type: "INTEGER", nullable: false), - Location = table.Column(type: "TEXT", nullable: true), + Location = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), IsActive = table.Column(type: "INTEGER", nullable: false), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true), - UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), - Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true, collation: "NOCASE"), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true, collation: "NOCASE"), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true, collation: "NOCASE"), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true, collation: "NOCASE"), EmailConfirmed = table.Column(type: "INTEGER", nullable: false), - PasswordHash = table.Column(type: "TEXT", nullable: true), - SecurityStamp = table.Column(type: "TEXT", nullable: true), - ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), - PhoneNumber = table.Column(type: "TEXT", nullable: true), + PasswordHash = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + SecurityStamp = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + PhoneNumber = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), LockoutEnd = table.Column(type: "TEXT", nullable: true), @@ -138,14 +140,14 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(type: "TEXT", maxLength: 450, nullable: false), - Title = table.Column(type: "TEXT", nullable: false), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + Name = table.Column(type: "TEXT", maxLength: 450, nullable: false, collation: "NOCASE"), + Title = table.Column(type: "TEXT", nullable: false, collation: "NOCASE"), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true) }, @@ -160,17 +162,17 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true), RoleId = table.Column(type: "INTEGER", nullable: false), - ClaimType = table.Column(type: "TEXT", nullable: true), - ClaimValue = table.Column(type: "TEXT", nullable: true) + ClaimType = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + ClaimValue = table.Column(type: "TEXT", nullable: true, collation: "NOCASE") }, constraints: table => { @@ -189,17 +191,17 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true), UserId = table.Column(type: "INTEGER", nullable: false), - ClaimType = table.Column(type: "TEXT", nullable: true), - ClaimValue = table.Column(type: "TEXT", nullable: true) + ClaimType = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), + ClaimValue = table.Column(type: "TEXT", nullable: true, collation: "NOCASE") }, constraints: table => { @@ -216,17 +218,17 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "AppUserLogins", columns: table => new { - LoginProvider = table.Column(type: "TEXT", nullable: false), - ProviderKey = table.Column(type: "TEXT", nullable: false), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + LoginProvider = table.Column(type: "TEXT", nullable: false, collation: "NOCASE"), + ProviderKey = table.Column(type: "TEXT", nullable: false, collation: "NOCASE"), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true), - ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true, collation: "NOCASE"), UserId = table.Column(type: "INTEGER", nullable: false) }, constraints: table => @@ -246,12 +248,12 @@ protected override void Up(MigrationBuilder migrationBuilder) { UserId = table.Column(type: "INTEGER", nullable: false), RoleId = table.Column(type: "INTEGER", nullable: false), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true) }, @@ -277,17 +279,17 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { UserId = table.Column(type: "INTEGER", nullable: false), - LoginProvider = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + LoginProvider = table.Column(type: "TEXT", nullable: false, collation: "NOCASE"), + Name = table.Column(type: "TEXT", nullable: false, collation: "NOCASE"), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true), - Value = table.Column(type: "TEXT", nullable: true) + Value = table.Column(type: "TEXT", nullable: true, collation: "NOCASE") }, constraints: table => { @@ -306,14 +308,14 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - HashedPassword = table.Column(type: "TEXT", maxLength: 450, nullable: false), + HashedPassword = table.Column(type: "TEXT", maxLength: 450, nullable: false, collation: "NOCASE"), UserId = table.Column(type: "INTEGER", nullable: false), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true) }, @@ -334,15 +336,15 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(type: "TEXT", maxLength: 450, nullable: false), + Name = table.Column(type: "TEXT", maxLength: 450, nullable: false, collation: "NOCASE"), Price = table.Column(type: "decimal(18, 6)", nullable: false), CategoryId = table.Column(type: "INTEGER", nullable: false), - CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + CreatedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + CreatedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), CreatedByUserId = table.Column(type: "INTEGER", nullable: true), CreatedDateTime = table.Column(type: "TEXT", nullable: true), - ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true), - ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true), + ModifiedByBrowserName = table.Column(type: "TEXT", maxLength: 1000, nullable: true, collation: "NOCASE"), + ModifiedByIp = table.Column(type: "TEXT", maxLength: 255, nullable: true, collation: "NOCASE"), ModifiedByUserId = table.Column(type: "INTEGER", nullable: true), ModifiedDateTime = table.Column(type: "TEXT", nullable: true) }, diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/SQLiteDbContextModelSnapshot.cs b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/SQLiteDbContextModelSnapshot.cs index 0c108b1..db4362f 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/SQLiteDbContextModelSnapshot.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/Migrations/SQLiteDbContextModelSnapshot.cs @@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +#nullable disable + namespace ASPNETCoreIdentitySample.DataLayer.SQLite.Migrations { [DbContext(typeof(SQLiteDbContext))] @@ -14,7 +16,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "5.0.8"); + .UseCollation("NOCASE") + .HasAnnotation("ProductVersion", "6.0.1"); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Category", b => { @@ -24,11 +27,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -38,11 +43,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -53,11 +60,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Title") .IsRequired() - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); @@ -71,17 +80,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("FriendlyName") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("XmlData") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); b.HasIndex("FriendlyName") .IsUnique(); - b.ToTable("AppDataProtectionKeys"); + b.ToTable("AppDataProtectionKeys", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.AppLogItem", b => @@ -92,11 +103,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -108,21 +121,26 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("LogLevel") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Logger") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Message") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -131,10 +149,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("StateJson") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Url") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); @@ -145,7 +165,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Property("Id") .HasMaxLength(449) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("AbsoluteExpiration") .HasColumnType("TEXT"); @@ -176,15 +197,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -193,15 +217,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("Description") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -211,11 +238,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("NormalizedName") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); @@ -223,7 +252,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsUnique() .HasDatabaseName("RoleNameIndex"); - b.ToTable("AppRoles"); + b.ToTable("AppRoles", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.RoleClaim", b => @@ -233,18 +262,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("ClaimType") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ClaimValue") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -254,11 +287,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -273,7 +308,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AppRoleClaims"); + b.ToTable("AppRoleClaims", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.User", b => @@ -290,15 +325,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -308,14 +346,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Email") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("EmailConfirmed") .HasColumnType("INTEGER"); b.Property("FirstName") .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("IsActive") .HasColumnType("INTEGER"); @@ -325,13 +365,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("LastName") .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("LastVisitDateTime") .HasColumnType("TEXT"); b.Property("Location") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("LockoutEnabled") .HasColumnType("INTEGER"); @@ -341,11 +383,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -355,34 +399,41 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("NormalizedEmail") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("NormalizedUserName") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("PasswordHash") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("PhoneNumber") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("PhoneNumberConfirmed") .HasColumnType("INTEGER"); b.Property("PhotoFileName") .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("SecurityStamp") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("TwoFactorEnabled") .HasColumnType("INTEGER"); b.Property("UserName") .HasMaxLength(256) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("Id"); @@ -393,7 +444,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsUnique() .HasDatabaseName("UserNameIndex"); - b.ToTable("AppUsers"); + b.ToTable("AppUsers", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserClaim", b => @@ -403,18 +454,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("ClaimType") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ClaimValue") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -424,11 +479,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -443,24 +500,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserClaims"); + b.ToTable("AppUserClaims", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserLogin", b => { b.Property("LoginProvider") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ProviderKey") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -470,11 +531,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -483,7 +546,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("ProviderDisplayName") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("UserId") .HasColumnType("INTEGER"); @@ -492,7 +556,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserLogins"); + b.ToTable("AppUserLogins", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserRole", b => @@ -505,11 +569,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -519,11 +585,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -535,7 +603,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("RoleId"); - b.ToTable("AppUserRoles"); + b.ToTable("AppUserRoles", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserToken", b => @@ -544,18 +612,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("LoginProvider") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Name") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -565,11 +637,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -578,11 +652,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("Value") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AppUserTokens"); + b.ToTable("AppUserTokens", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Identity.UserUsedPassword", b => @@ -593,11 +668,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -608,15 +685,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("HashedPassword") .IsRequired() .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -631,7 +711,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("UserId"); - b.ToTable("AppUserUsedPasswords"); + b.ToTable("AppUserUsedPasswords", (string)null); }); modelBuilder.Entity("ASPNETCoreIdentitySample.Entities.Product", b => @@ -645,11 +725,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("CreatedByUserId") .HasColumnType("INTEGER"); @@ -659,11 +741,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedByBrowserName") .HasMaxLength(1000) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByIp") .HasMaxLength(255) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("ModifiedByUserId") .HasColumnType("INTEGER"); @@ -674,7 +758,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() .HasMaxLength(450) - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .UseCollation("NOCASE"); b.Property("Price") .HasColumnType("decimal(18, 6)"); diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteContextFactory.cs b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteContextFactory.cs index e753d18..3eb6e35 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteContextFactory.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteContextFactory.cs @@ -1,43 +1,42 @@ -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using ASPNETCoreIdentitySample.DataLayer.Context; -using System; -using System.IO; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace ASPNETCoreIdentitySample.DataLayer.SQLite +namespace ASPNETCoreIdentitySample.DataLayer.SQLite; + +public class SQLiteContextFactory : IDesignTimeDbContextFactory { - public class SQLiteContextFactory : IDesignTimeDbContextFactory + public SQLiteDbContext CreateDbContext(string[] args) { - public SQLiteDbContext CreateDbContext(string[] args) - { - var services = new ServiceCollection(); - services.AddOptions(); - services.AddSingleton(); - services.AddSingleton(); + var services = new ServiceCollection(); + services.AddOptions(); + services.AddLogging(cfg => cfg.AddConsole().AddDebug()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - var basePath = Directory.GetCurrentDirectory(); - Console.WriteLine($"Using `{basePath}` as the ContentRootPath"); - var configuration = new ConfigurationBuilder() - .SetBasePath(basePath) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .Build(); - services.AddSingleton(provider => configuration); - services.Configure(options => configuration.Bind(options)); + var basePath = Directory.GetCurrentDirectory(); + WriteLine($"Using `{basePath}` as the ContentRootPath"); + var configuration = new ConfigurationBuilder() + .SetBasePath(basePath) + .AddJsonFile("appsettings.json", false, true) + .Build(); + services.AddSingleton(_ => configuration); + services.Configure(options => configuration.Bind(options)); - var siteSettings = services.BuildServiceProvider().GetRequiredService>(); - siteSettings.Value.ActiveDatabase = ActiveDatabase.SQLite; + var buildServiceProvider = services.BuildServiceProvider(); + var siteSettings = buildServiceProvider.GetRequiredService>(); + siteSettings.Value.ActiveDatabase = ActiveDatabase.SQLite; - services.AddEntityFrameworkSqlite(); // It's added to access services from the dbcontext, remove it if you are using the normal `AddDbContext` and normal constructor dependency injection. - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseConfiguredSQLite(siteSettings.Value, services.BuildServiceProvider()); + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseConfiguredSQLite(siteSettings.Value, buildServiceProvider); - return new SQLiteDbContext(optionsBuilder.Options); - } + return new SQLiteDbContext(optionsBuilder.Options); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteDbContext.cs b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteDbContext.cs index f60bde2..c0b4c48 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteDbContext.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteDbContext.cs @@ -1,20 +1,22 @@ +using ASPNETCoreIdentitySample.Common.EFCoreToolkit; using ASPNETCoreIdentitySample.DataLayer.Context; using Microsoft.EntityFrameworkCore; -using ASPNETCoreIdentitySample.Common.EFCoreToolkit; -namespace ASPNETCoreIdentitySample.DataLayer.SQLite +namespace ASPNETCoreIdentitySample.DataLayer.SQLite; + +public class SQLiteDbContext : ApplicationDbContext { - public class SQLiteDbContext : ApplicationDbContext + public SQLiteDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) { - public SQLiteDbContext(DbContextOptions options) : base(options) - { - } + base.OnModelCreating(builder); - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); + // NOTE: Add custom SQLite's settings here ... - builder.AddDateTimeOffsetConverter(); - } + builder.AddDateTimeOffsetConverter(); + builder.SetCaseInsensitiveSearchesForSQLite(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteServiceCollectionExtensions.cs b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteServiceCollectionExtensions.cs index 6fed6ad..c28e4dd 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteServiceCollectionExtensions.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/SQLiteServiceCollectionExtensions.cs @@ -1,72 +1,103 @@ -using System; using ASPNETCoreIdentitySample.Common.PersianToolkit; using ASPNETCoreIdentitySample.Common.WebToolkit; using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; -namespace ASPNETCoreIdentitySample.DataLayer.SQLite +namespace ASPNETCoreIdentitySample.DataLayer.SQLite; + +public static class SQLiteServiceCollectionExtensions { - public static class SQLiteServiceCollectionExtensions + public static IServiceCollection AddConfiguredSQLiteDbContext(this IServiceCollection services, + SiteSettings siteSettings) { - public static IServiceCollection AddConfiguredSQLiteDbContext(this IServiceCollection services, - SiteSettings siteSettings) + services.AddScoped(serviceProvider => { - services.AddScoped(serviceProvider => - serviceProvider.GetRequiredService()); - services.AddEntityFrameworkSqlite(); // It's added to access services from the dbcontext, remove it if you are using the normal `AddDbContext` and normal constructor dependency injection. - services.AddDbContextPool( - (serviceProvider, optionsBuilder) => optionsBuilder.UseConfiguredSQLite(siteSettings, serviceProvider)); - return services; + var context = serviceProvider.GetRequiredService(); + SetCascadeOnSaveChanges(context); + return context; + }); + services.AddDbContextPool( + (serviceProvider, optionsBuilder) => optionsBuilder.UseConfiguredSQLite(siteSettings, serviceProvider)); + return services; + } + + private static void SetCascadeOnSaveChanges(DbContext 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) + { + if (optionsBuilder == null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); } - public static void UseConfiguredSQLite( - this DbContextOptionsBuilder optionsBuilder, SiteSettings siteSettings, IServiceProvider serviceProvider) + if (siteSettings == null) { - var connectionString = siteSettings.GetSQLiteDbConnectionString(); - optionsBuilder.UseSqlite( - connectionString, - sqlServerOptionsBuilder => - { - sqlServerOptionsBuilder.CommandTimeout((int)TimeSpan.FromMinutes(3).TotalSeconds); - sqlServerOptionsBuilder.MigrationsAssembly(typeof(SQLiteServiceCollectionExtensions).Assembly - .FullName); - }); - optionsBuilder - .UseInternalServiceProvider( - serviceProvider); // It's added to access services from the dbcontext, remove it if you are using the normal `AddDbContext` and normal constructor dependency injection. - optionsBuilder.AddInterceptors(new PersianYeKeCommandInterceptor()); - optionsBuilder.ConfigureWarnings(warnings => + throw new ArgumentNullException(nameof(siteSettings)); + } + + var connectionString = siteSettings.GetSQLiteDbConnectionString(); + optionsBuilder.UseSqlite( + connectionString, + sqlServerOptionsBuilder => { - // ... + sqlServerOptionsBuilder.CommandTimeout((int)TimeSpan.FromMinutes(3).TotalSeconds); + sqlServerOptionsBuilder.MigrationsAssembly(typeof(SQLiteServiceCollectionExtensions).Assembly + .FullName); }); - } + optionsBuilder.AddInterceptors(new PersianYeKeCommandInterceptor(), + serviceProvider.GetRequiredService()); + optionsBuilder.ConfigureWarnings(warnings => + { + warnings.Log( + (CoreEventId.LazyLoadOnDisposedContextWarning, LogLevel.Warning), + (CoreEventId.DetachedLazyLoadingWarning, LogLevel.Warning), + (CoreEventId.ManyServiceProvidersCreatedWarning, LogLevel.Warning), + (CoreEventId.SensitiveDataLoggingEnabledWarning, LogLevel.Information) + ); + }); + optionsBuilder.EnableSensitiveDataLogging().EnableDetailedErrors(); + } - public static string GetSQLiteDbConnectionString(this SiteSettings siteSettingsValue) + public static string GetSQLiteDbConnectionString(this SiteSettings siteSettingsValue) + { + if (siteSettingsValue == null) { - if (siteSettingsValue == null) - { - throw new ArgumentNullException(nameof(siteSettingsValue)); - } + throw new ArgumentNullException(nameof(siteSettingsValue)); + } - switch (siteSettingsValue.ActiveDatabase) - { - case ActiveDatabase.SQLite: - return siteSettingsValue.ConnectionStrings - .SQLite - .ApplicationDbContextConnection - .ReplaceDataDirectoryInConnectionString(); + switch (siteSettingsValue.ActiveDatabase) + { + case ActiveDatabase.SQLite: + return siteSettingsValue.ConnectionStrings + .SQLite + .ApplicationDbContextConnection + .ReplaceDataDirectoryInConnectionString(); - default: - throw new NotSupportedException( - "Please set the ActiveDatabase in appsettings.json file to `SQLite`."); - } + default: + throw new NotSupportedException( + "Please set the ActiveDatabase in appsettings.json file to `SQLite`."); } + } - public static string ReplaceDataDirectoryInConnectionString(this string connectionString) + public static string ReplaceDataDirectoryInConnectionString(this string connectionString) + { + if (connectionString == null) { - return connectionString.Replace("|DataDirectory|", ServerInfo.GetAppDataFolderPath()); + return null; } + + return connectionString.Replace("|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 0ac3ee7..39ba9bc 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 5.0.8 +dotnet tool update --global dotnet-ef --version 6.0.1 dotnet build dotnet ef migrations --startup-project ../ASPNETCoreIdentitySample/ add V%mydate%_%mytime% --context SQLiteDbContext pause \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_02-update_db.cmd b/src/ASPNETCoreIdentitySample.DataLayer.SQLite/_02-update_db.cmd index 5b9d2ad..528fd52 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 5.0.8 +dotnet tool update --global dotnet-ef --version 6.0.1 dotnet build dotnet ef --startup-project ../ASPNETCoreIdentitySample/ database update --context SQLiteDbContext pause \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/ASPNETCoreIdentitySample.DataLayer.csproj b/src/ASPNETCoreIdentitySample.DataLayer/ASPNETCoreIdentitySample.DataLayer.csproj index b1a0780..dd900f5 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/ASPNETCoreIdentitySample.DataLayer.csproj +++ b/src/ASPNETCoreIdentitySample.DataLayer/ASPNETCoreIdentitySample.DataLayer.csproj @@ -1,29 +1,28 @@  - net5.0 - RCS1090 + net6.0 - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - - + + + + + + diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/AppDataProtectionKeyConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/AppDataProtectionKeyConfiguration.cs index 90a2716..7c1486a 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/AppDataProtectionKeyConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/AppDataProtectionKeyConfiguration.cs @@ -2,14 +2,18 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class AppDataProtectionKeyConfiguration : IEntityTypeConfiguration { - public class AppDataProtectionKeyConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.ToTable("AppDataProtectionKeys"); - builder.HasIndex(e => e.FriendlyName).IsUnique(); + throw new ArgumentNullException(nameof(builder)); } + + builder.ToTable("AppDataProtectionKeys"); + builder.HasIndex(e => e.FriendlyName).IsUnique(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/AppSqlCacheConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/AppSqlCacheConfiguration.cs index c2050d6..d6f144e 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/AppSqlCacheConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/AppSqlCacheConfiguration.cs @@ -1,27 +1,22 @@ using ASPNETCoreIdentitySample.Entities.Identity; -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class AppSqlCacheConfiguration : IEntityTypeConfiguration { - public class AppSqlCacheConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - private readonly SiteSettings _siteSettings; - - public AppSqlCacheConfiguration(SiteSettings siteSettings) + if (builder == null) { - _siteSettings = siteSettings; + throw new ArgumentNullException(nameof(builder)); } - public void Configure(EntityTypeBuilder builder) - { - // For Microsoft.Extensions.Caching.SqlServer - var cacheOptions = _siteSettings.CookieOptions.DistributedSqlServerCacheOptions; - builder.ToTable(cacheOptions.TableName, cacheOptions.SchemaName); - builder.HasIndex(e => e.ExpiresAtTime).HasDatabaseName("Index_ExpiresAtTime"); - builder.Property(e => e.Id).HasMaxLength(449); - builder.Property(e => e.Value).IsRequired(); - } + // For Microsoft.Extensions.Caching.SqlServer + builder.ToTable("AppSqlCache", "dbo"); + builder.HasIndex(e => e.ExpiresAtTime).HasDatabaseName("Index_ExpiresAtTime"); + builder.Property(e => e.Id).HasMaxLength(449); + builder.Property(e => e.Value).IsRequired(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/CategoryConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/CategoryConfiguration.cs index 8a844de..3b6ee17 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/CategoryConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/CategoryConfiguration.cs @@ -2,14 +2,18 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class CategoryConfiguration : IEntityTypeConfiguration { - public class CategoryConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.Property(category => category.Name).HasMaxLength(450).IsRequired(); - builder.Property(category => category.Title).IsRequired(); + throw new ArgumentNullException(nameof(builder)); } + + builder.Property(category => category.Name).HasMaxLength(450).IsRequired(); + builder.Property(category => category.Title).IsRequired(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/IdentityMappings.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/IdentityMappings.cs index d269f11..cca3435 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/IdentityMappings.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/IdentityMappings.cs @@ -1,21 +1,24 @@ -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public static class IdentityMappings { - public static class IdentityMappings + /// + /// Adds all of the ASP.NET Core Identity related mappings at once. + /// More info: http://www.dntips.ir/post/2577 + /// and http://www.dntips.ir/post/2578 + /// + public static void AddCustomIdentityMappings(this ModelBuilder modelBuilder) { - /// - /// Adds all of the ASP.NET Core Identity related mappings at once. - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// - public static void AddCustomIdentityMappings(this ModelBuilder modelBuilder, SiteSettings siteSettings) + if (modelBuilder == null) { - modelBuilder.ApplyConfigurationsFromAssembly(typeof(IdentityMappings).Assembly); - - // IEntityTypeConfiguration's which have constructors with parameters - modelBuilder.ApplyConfiguration(new AppSqlCacheConfiguration(siteSettings)); + throw new ArgumentNullException(nameof(modelBuilder)); } + + modelBuilder.ApplyConfigurationsFromAssembly(typeof(IdentityMappings).Assembly); + + // IEntityTypeConfiguration's which have constructors with parameters + modelBuilder.ApplyConfiguration(new AppSqlCacheConfiguration()); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/ProductConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/ProductConfiguration.cs index c513fa6..1f1e124 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/ProductConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/ProductConfiguration.cs @@ -2,15 +2,19 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class ProductConfiguration : IEntityTypeConfiguration { - public class ProductConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.Property(product => product.Name).HasMaxLength(450).IsRequired(); - builder.HasOne(product => product.Category) - .WithMany(category => category.Products); + throw new ArgumentNullException(nameof(builder)); } + + builder.Property(product => product.Name).HasMaxLength(450).IsRequired(); + builder.HasOne(product => product.Category) + .WithMany(category => category.Products); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/RoleClaimConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/RoleClaimConfiguration.cs index 53c76c9..c39ce3a 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/RoleClaimConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/RoleClaimConfiguration.cs @@ -2,17 +2,21 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class RoleClaimConfiguration : IEntityTypeConfiguration { - public class RoleClaimConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.HasOne(roleClaim => roleClaim.Role) - .WithMany(role => role.Claims) - .HasForeignKey(roleClaim => roleClaim.RoleId); - - builder.ToTable("AppRoleClaims"); + throw new ArgumentNullException(nameof(builder)); } + + builder.HasOne(roleClaim => roleClaim.Role) + .WithMany(role => role.Claims) + .HasForeignKey(roleClaim => roleClaim.RoleId); + + builder.ToTable("AppRoleClaims"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/RoleConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/RoleConfiguration.cs index db53069..638b374 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/RoleConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/RoleConfiguration.cs @@ -2,13 +2,17 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class RoleConfiguration : IEntityTypeConfiguration { - public class RoleConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.ToTable("AppRoles"); + throw new ArgumentNullException(nameof(builder)); } + + builder.ToTable("AppRoles"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserClaimConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserClaimConfiguration.cs index 4e252fa..8fd86f1 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserClaimConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserClaimConfiguration.cs @@ -2,17 +2,21 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class UserClaimConfiguration : IEntityTypeConfiguration { - public class UserClaimConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.HasOne(userClaim => userClaim.User) - .WithMany(user => user.Claims) - .HasForeignKey(userClaim => userClaim.UserId); - - builder.ToTable("AppUserClaims"); + throw new ArgumentNullException(nameof(builder)); } + + builder.HasOne(userClaim => userClaim.User) + .WithMany(user => user.Claims) + .HasForeignKey(userClaim => userClaim.UserId); + + builder.ToTable("AppUserClaims"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserConfiguration.cs index 06d2475..38f4d83 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserConfiguration.cs @@ -2,13 +2,17 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class UserConfiguration : IEntityTypeConfiguration { - public class UserConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.ToTable("AppUsers"); + throw new ArgumentNullException(nameof(builder)); } + + builder.ToTable("AppUsers"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserLoginConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserLoginConfiguration.cs index fb9370f..978a081 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserLoginConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserLoginConfiguration.cs @@ -2,17 +2,21 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class UserLoginConfiguration : IEntityTypeConfiguration { - public class UserLoginConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.HasOne(userLogin => userLogin.User) - .WithMany(user => user.Logins) - .HasForeignKey(userLogin => userLogin.UserId); - - builder.ToTable("AppUserLogins"); + throw new ArgumentNullException(nameof(builder)); } + + builder.HasOne(userLogin => userLogin.User) + .WithMany(user => user.Logins) + .HasForeignKey(userLogin => userLogin.UserId); + + builder.ToTable("AppUserLogins"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserRoleConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserRoleConfiguration.cs index 2088a9f..971beb8 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserRoleConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserRoleConfiguration.cs @@ -2,21 +2,25 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class UserRoleConfiguration : IEntityTypeConfiguration { - public class UserRoleConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.HasOne(userRole => userRole.Role) - .WithMany(role => role.Users) - .HasForeignKey(userRole => userRole.RoleId); + throw new ArgumentNullException(nameof(builder)); + } - builder.HasOne(userRole => userRole.User) - .WithMany(user => user.Roles) - .HasForeignKey(userRole => userRole.UserId); + builder.HasOne(userRole => userRole.Role) + .WithMany(role => role.Users) + .HasForeignKey(userRole => userRole.RoleId); - builder.ToTable("AppUserRoles"); - } + builder.HasOne(userRole => userRole.User) + .WithMany(user => user.Roles) + .HasForeignKey(userRole => userRole.UserId); + + builder.ToTable("AppUserRoles"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserTokenConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserTokenConfiguration.cs index 23543b1..9c723bc 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserTokenConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserTokenConfiguration.cs @@ -2,17 +2,21 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class UserTokenConfiguration : IEntityTypeConfiguration { - public class UserTokenConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.HasOne(userToken => userToken.User) - .WithMany(user => user.UserTokens) - .HasForeignKey(userToken => userToken.UserId); - - builder.ToTable("AppUserTokens"); + throw new ArgumentNullException(nameof(builder)); } + + builder.HasOne(userToken => userToken.User) + .WithMany(user => user.UserTokens) + .HasForeignKey(userToken => userToken.UserId); + + builder.ToTable("AppUserTokens"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserUsedPasswordConfiguration.cs b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserUsedPasswordConfiguration.cs index 1e84e32..51809c5 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserUsedPasswordConfiguration.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Configurations/UserUsedPasswordConfiguration.cs @@ -2,20 +2,24 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace ASPNETCoreIdentitySample.DataLayer.Mappings +namespace ASPNETCoreIdentitySample.DataLayer.Configurations; + +public class UserUsedPasswordConfiguration : IEntityTypeConfiguration { - public class UserUsedPasswordConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder == null) { - builder.ToTable("AppUserUsedPasswords"); + throw new ArgumentNullException(nameof(builder)); + } - builder.Property(applicationUserUsedPassword => applicationUserUsedPassword.HashedPassword) - .HasMaxLength(450) - .IsRequired(); + builder.ToTable("AppUserUsedPasswords"); - builder.HasOne(applicationUserUsedPassword => applicationUserUsedPassword.User) - .WithMany(applicationUser => applicationUser.UserUsedPasswords); - } + builder.Property(applicationUserUsedPassword => applicationUserUsedPassword.HashedPassword) + .HasMaxLength(450) + .IsRequired(); + + builder.HasOne(applicationUserUsedPassword => applicationUserUsedPassword.User) + .WithMany(applicationUser => applicationUser.UserUsedPasswords); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Context/ApplicationDbContext.cs b/src/ASPNETCoreIdentitySample.DataLayer/Context/ApplicationDbContext.cs index 94d15cb..39d2455 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Context/ApplicationDbContext.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Context/ApplicationDbContext.cs @@ -1,208 +1,149 @@ -using ASPNETCoreIdentitySample.Common.GuardToolkit; +using ASPNETCoreIdentitySample.Common.EFCoreToolkit; +using ASPNETCoreIdentitySample.DataLayer.Configurations; +using ASPNETCoreIdentitySample.Entities; using ASPNETCoreIdentitySample.Entities.AuditableEntity; using ASPNETCoreIdentitySample.Entities.Identity; -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System.Collections.Generic; -using System.Globalization; -using System.Threading.Tasks; -using System.Threading; -using System; -using ASPNETCoreIdentitySample.Entities; -using ASPNETCoreIdentitySample.DataLayer.Mappings; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; -using ASPNETCoreIdentitySample.Common.EFCoreToolkit; - -namespace ASPNETCoreIdentitySample.DataLayer.Context -{ - /// - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// plus http://www.dotnettips.info/post/2491 - /// - public class ApplicationDbContext : - IdentityDbContext, - IUnitOfWork - { - private IDbContextTransaction _transaction; - - // we can't use constructor injection anymore, because we are using the `AddDbContextPool<>` - public ApplicationDbContext(DbContextOptions options) - : base(options) { } - - #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 BeginTransaction() - { - _transaction = Database.BeginTransaction(); - } +namespace ASPNETCoreIdentitySample.DataLayer.Context; - public void RollbackTransaction() - { - if (_transaction == null) - { - throw new NullReferenceException("Please call `BeginTransaction()` method first."); - } - _transaction.Rollback(); - } - - public void CommitTransaction() - { - if (_transaction == null) - { - throw new NullReferenceException("Please call `BeginTransaction()` method first."); - } - _transaction.Commit(); - } +/// +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// plus http://www.dntips.ir/post/2491 +/// +public class ApplicationDbContext : + IdentityDbContext, + IUnitOfWork +{ + private bool _isDisposed; + private IDbContextTransaction _transaction; - public override void Dispose() - { - _transaction?.Dispose(); - base.Dispose(); - } + // we can't use constructor injection anymore, because we are using the `AddDbContextPool<>` + public ApplicationDbContext(DbContextOptions options) + : base(options) + { + } - public void ExecuteSqlInterpolatedCommand(FormattableString query) - { - Database.ExecuteSqlInterpolated(query); - } + public virtual DbSet Categories { set; get; } + public virtual DbSet Products { set; get; } - public void ExecuteSqlRawCommand(string query, params object[] parameters) - { - Database.ExecuteSqlRaw(query, parameters); - } + protected override void OnModelCreating(ModelBuilder builder) + { + // it should be placed here, otherwise it will rewrite the following settings! + base.OnModelCreating(builder); - public T GetShadowPropertyValue(object entity, string propertyName) where T : IConvertible - { - var value = this.Entry(entity).Property(propertyName).CurrentValue; - return value != null - ? (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture) - : default; - } + // we can't use constructor injection anymore, because we are using the `AddDbContextPool<>` + // Adds all of the ASP.NET Core Identity related mappings at once. + builder.AddCustomIdentityMappings(); - public object GetShadowPropertyValue(object entity, string propertyName) - { - return this.Entry(entity).Property(propertyName).CurrentValue; - } + // Custom application mappings + builder.SetDecimalPrecision(); + builder.AddDateTimeUtcKindConverter(); - public void MarkAsChanged(TEntity entity) where TEntity : class - { - Update(entity); - } + // This should be placed here, at the end. + builder.AddAuditableShadowProperties(); + } - public void RemoveRange(IEnumerable entities) where TEntity : class - { - Set().RemoveRange(entities); - } + #region BaseClass - public override int SaveChanges(bool acceptAllChangesOnSuccess) - { - ChangeTracker.DetectChanges(); + public virtual DbSet AppLogItems { get; set; } + public virtual DbSet AppSqlCache { get; set; } + public virtual DbSet AppDataProtectionKeys { get; set; } - beforeSaveTriggers(); + public void AddRange(IEnumerable entities) where TEntity : class + { + Set().AddRange(entities); + } - ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again. - var result = base.SaveChanges(acceptAllChangesOnSuccess); - ChangeTracker.AutoDetectChangesEnabled = true; - return result; - } + public void BeginTransaction() + { + _transaction = Database.BeginTransaction(); + } - public override int SaveChanges() + public void RollbackTransaction() + { + if (_transaction == null) { - ChangeTracker.DetectChanges(); //NOTE: changeTracker.Entries() will call it automatically. - - beforeSaveTriggers(); - - ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again. - var result = base.SaveChanges(); - ChangeTracker.AutoDetectChangesEnabled = true; - return result; + throw new InvalidOperationException("Please call `BeginTransaction()` method first."); } - public override Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) - { - ChangeTracker.DetectChanges(); - - beforeSaveTriggers(); - - ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again. - var result = base.SaveChangesAsync(cancellationToken); - ChangeTracker.AutoDetectChangesEnabled = true; - return result; - } + _transaction.Rollback(); + } - public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken()) + public void CommitTransaction() + { + if (_transaction == null) { - ChangeTracker.DetectChanges(); - - beforeSaveTriggers(); - - ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again. - var result = base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); - ChangeTracker.AutoDetectChangesEnabled = true; - return result; + throw new InvalidOperationException("Please call `BeginTransaction()` method first."); } - private void beforeSaveTriggers() - { - validateEntities(); - setShadowProperties(); - } + _transaction.Commit(); + } - private void setShadowProperties() - { - // we can't use constructor injection anymore, because we are using the `AddDbContextPool<>` - var props = this.GetService()?.GetShadowProperties(); - ChangeTracker.SetAuditableEntityPropertyValues(props); - } + [SuppressMessage("Microsoft.Usage", "CA2215:Dispose methods should call base class dispose", + Justification = "base.Dispose() is called")] + public sealed override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - private void validateEntities() + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) { - var errors = this.GetValidationErrors(); - if (!string.IsNullOrWhiteSpace(errors)) + try + { + if (disposing) + { + _transaction?.Dispose(); + _transaction = null; + } + } + finally { - // we can't use constructor injection anymore, because we are using the `AddDbContextPool<>` - var loggerFactory = this.GetService(); - var logger = loggerFactory.CreateLogger(); - logger.LogError(errors); - throw new InvalidOperationException(errors); + _isDisposed = true; } } - #endregion + base.Dispose(); + } - public virtual DbSet Categories { set; get; } - public virtual DbSet Products { set; get; } + public void ExecuteSqlInterpolatedCommand(FormattableString query) + { + Database.ExecuteSqlInterpolated(query); + } - protected override void OnModelCreating(ModelBuilder builder) - { - // it should be placed here, otherwise it will rewrite the following settings! - base.OnModelCreating(builder); + public void ExecuteSqlRawCommand(string query, params object[] parameters) + { + Database.ExecuteSqlRaw(query, parameters); + } - // we can't use constructor injection anymore, because we are using the `AddDbContextPool<>` - // Adds all of the ASP.NET Core Identity related mappings at once. - builder.AddCustomIdentityMappings(this.GetService>()?.Value); + 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; + } - // Custom application mappings - builder.SetDecimalPrecision(); - builder.AddDateTimeUtcKindConverter(); + public object GetShadowPropertyValue(object entity, string propertyName) + { + return Entry(entity).Property(propertyName).CurrentValue; + } - // This should be placed here, at the end. - builder.AddAuditableShadowProperties(); - } + public void MarkAsChanged(TEntity entity) where TEntity : class + { + Update(entity); } + + 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 new file mode 100644 index 0000000..053073e --- /dev/null +++ b/src/ASPNETCoreIdentitySample.DataLayer/Context/AuditableEntitiesInterceptor.cs @@ -0,0 +1,80 @@ +using ASPNETCoreIdentitySample.Common.GuardToolkit; +using ASPNETCoreIdentitySample.Entities.AuditableEntity; +using DNTCommon.Web.Core; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace ASPNETCoreIdentitySample.DataLayer.Context; + +public class AuditableEntitiesInterceptor : SaveChangesInterceptor +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ILogger _logger; + + public AuditableEntitiesInterceptor( + IHttpContextAccessor httpContextAccessor, + ILogger logger) + { + _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public override InterceptionResult SavingChanges( + DbContextEventData eventData, + InterceptionResult result) + { + if (eventData == null) + { + throw new ArgumentNullException(nameof(eventData)); + } + + BeforeSaveTriggers(eventData.Context); + return result; + } + + public override ValueTask> SavingChangesAsync( + DbContextEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + { + if (eventData == null) + { + throw new ArgumentNullException(nameof(eventData)); + } + + BeforeSaveTriggers(eventData.Context); + return ValueTask.FromResult(result); + } + + private void BeforeSaveTriggers(DbContext context) + { + ValidateEntities(context); + ApplyAudits(context?.ChangeTracker); + } + + private void ValidateEntities(DbContext context) + { + var errors = context?.GetValidationErrors(); + if (string.IsNullOrWhiteSpace(errors)) + { + return; + } + + _logger.LogErrorMessage(errors); + throw new InvalidOperationException(errors); + } + + private void ApplyAudits(ChangeTracker changeTracker) + { + if (changeTracker is null) + { + return; + } + + var props = _httpContextAccessor?.GetShadowProperties(); + changeTracker.SetAuditableEntityPropertyValues(props); + } +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.DataLayer/Context/IUnitOfWork.cs b/src/ASPNETCoreIdentitySample.DataLayer/Context/IUnitOfWork.cs index 2dc68e4..5b11692 100644 --- a/src/ASPNETCoreIdentitySample.DataLayer/Context/IUnitOfWork.cs +++ b/src/ASPNETCoreIdentitySample.DataLayer/Context/IUnitOfWork.cs @@ -1,37 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; -namespace ASPNETCoreIdentitySample.DataLayer.Context +namespace ASPNETCoreIdentitySample.DataLayer.Context; + +/// +/// More info: http://www.dntips.ir/post/2509 +/// +public interface IUnitOfWork : IDisposable { - /// - /// More info: http://www.dotnettips.info/post/2509 - /// - public interface IUnitOfWork : IDisposable - { - DbSet Set() where TEntity : class; + DbSet Set() where TEntity : class; - void AddRange(IEnumerable entities) where TEntity : class; - void RemoveRange(IEnumerable entities) where TEntity : class; + void AddRange(IEnumerable entities) where TEntity : class; + void RemoveRange(IEnumerable entities) where TEntity : class; - EntityEntry Entry(TEntity entity) where TEntity : class; - void MarkAsChanged(TEntity entity) where TEntity : class; - T GetShadowPropertyValue(object entity, string propertyName) where T : IConvertible; - object GetShadowPropertyValue(object entity, string propertyName); + EntityEntry Entry(TEntity entity) where TEntity : class; + void MarkAsChanged(TEntity entity) where TEntity : class; + T GetShadowPropertyValue(object entity, string propertyName) where T : IConvertible; + object GetShadowPropertyValue(object entity, string propertyName); - void ExecuteSqlInterpolatedCommand(FormattableString query); - void ExecuteSqlRawCommand(string query, params object[] parameters); + void ExecuteSqlInterpolatedCommand(FormattableString query); + void ExecuteSqlRawCommand(string query, params object[] parameters); - void BeginTransaction(); - void RollbackTransaction(); - void CommitTransaction(); + void BeginTransaction(); + void RollbackTransaction(); + void CommitTransaction(); - int SaveChanges(bool acceptAllChangesOnSuccess); - int SaveChanges(); - Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken()); - Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()); - } + int SaveChanges(bool acceptAllChangesOnSuccess); + int SaveChanges(); + Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new()); + Task SaveChangesAsync(CancellationToken cancellationToken = new()); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/ASPNETCoreIdentitySample.Entities.csproj b/src/ASPNETCoreIdentitySample.Entities/ASPNETCoreIdentitySample.Entities.csproj index fe3351a..2e56486 100644 --- a/src/ASPNETCoreIdentitySample.Entities/ASPNETCoreIdentitySample.Entities.csproj +++ b/src/ASPNETCoreIdentitySample.Entities/ASPNETCoreIdentitySample.Entities.csproj @@ -1,16 +1,15 @@  - net5.0 - RCS1090 + net6.0 - - - + + + - + diff --git a/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/AppShadowProperties.cs b/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/AppShadowProperties.cs new file mode 100644 index 0000000..ec06725 --- /dev/null +++ b/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/AppShadowProperties.cs @@ -0,0 +1,9 @@ +namespace ASPNETCoreIdentitySample.Entities.AuditableEntity; + +public class AppShadowProperties +{ + public string UserAgent { set; get; } + public string UserIp { set; get; } + public DateTime Now { set; get; } + public int? UserId { set; get; } +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/AuditableShadowProperties.cs b/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/AuditableShadowProperties.cs index b137b0b..1006ceb 100644 --- a/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/AuditableShadowProperties.cs +++ b/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/AuditableShadowProperties.cs @@ -1,181 +1,240 @@ -using System; -using System.Linq; -using Microsoft.EntityFrameworkCore; +using ASPNETCoreIdentitySample.Entities.Identity; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; -using ASPNETCoreIdentitySample.Entities.Identity; -using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample.Entities.AuditableEntity +namespace ASPNETCoreIdentitySample.Entities.AuditableEntity; + +/// +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// and http://www.dntips.ir/post/2507 +/// and http://www.dntips.ir/post/2232 +/// +public static class AuditableShadowProperties { - public class AppShadowProperties + public static readonly Func EFPropertyCreatedByBrowserName = + entity => EF.Property(entity, CreatedByBrowserName); + + public static readonly string CreatedByBrowserName = nameof(CreatedByBrowserName); + + public static readonly Func EFPropertyModifiedByBrowserName = + entity => EF.Property(entity, ModifiedByBrowserName); + + public static readonly string ModifiedByBrowserName = nameof(ModifiedByBrowserName); + + public static readonly Func EFPropertyCreatedByIp = + entity => EF.Property(entity, CreatedByIp); + + public static readonly string CreatedByIp = nameof(CreatedByIp); + + public static readonly Func EFPropertyModifiedByIp = + entity => EF.Property(entity, ModifiedByIp); + + public static readonly string ModifiedByIp = nameof(ModifiedByIp); + + public static readonly Func EFPropertyCreatedByUserId = + entity => EF.Property(entity, CreatedByUserId); + + public static readonly string CreatedByUserId = nameof(CreatedByUserId); + + public static readonly Func EFPropertyModifiedByUserId = + entity => EF.Property(entity, ModifiedByUserId); + + public static readonly string ModifiedByUserId = nameof(ModifiedByUserId); + + public static readonly Func EFPropertyCreatedDateTime = + entity => EF.Property(entity, CreatedDateTime); + + public static readonly string CreatedDateTime = nameof(CreatedDateTime); + + public static readonly Func EFPropertyModifiedDateTime = + entity => EF.Property(entity, ModifiedDateTime); + + public static readonly string ModifiedDateTime = nameof(ModifiedDateTime); + + public static void AddAuditableShadowProperties(this ModelBuilder modelBuilder) { - public string UserAgent { set; get; } - public string UserIp { set; get; } - public DateTime Now { set; get; } - public int? UserId { set; get; } + if (modelBuilder == null) + { + throw new ArgumentNullException(nameof(modelBuilder)); + } + + foreach (var clrType in modelBuilder.Model + .GetEntityTypes() + .Where(e => typeof(IAuditableEntity).IsAssignableFrom(e.ClrType)) + .Select(e => e.ClrType)) + { + modelBuilder.Entity(clrType) + .Property(CreatedByBrowserName).HasMaxLength(1000); + modelBuilder.Entity(clrType) + .Property(ModifiedByBrowserName).HasMaxLength(1000); + + modelBuilder.Entity(clrType) + .Property(CreatedByIp).HasMaxLength(255); + modelBuilder.Entity(clrType) + .Property(ModifiedByIp).HasMaxLength(255); + + modelBuilder.Entity(clrType) + .Property(CreatedByUserId); + modelBuilder.Entity(clrType) + .Property(ModifiedByUserId); + + modelBuilder.Entity(clrType) + .Property(CreatedDateTime); + modelBuilder.Entity(clrType) + .Property(ModifiedDateTime); + } } /// - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// and http://www.dotnettips.info/post/2507 - /// and http://www.dotnettips.info/post/2232 + /// More info: http://www.dntips.ir/post/2507 /// - public static class AuditableShadowProperties + public static void SetAuditableEntityPropertyValues( + this ChangeTracker changeTracker, + AppShadowProperties props) { - public static readonly Func EFPropertyCreatedByBrowserName = - entity => EF.Property(entity, CreatedByBrowserName); - public static readonly string CreatedByBrowserName = nameof(CreatedByBrowserName); - - public static readonly Func EFPropertyModifiedByBrowserName = - entity => EF.Property(entity, ModifiedByBrowserName); - public static readonly string ModifiedByBrowserName = nameof(ModifiedByBrowserName); - - public static readonly Func EFPropertyCreatedByIp = - entity => EF.Property(entity, CreatedByIp); - public static readonly string CreatedByIp = nameof(CreatedByIp); - - public static readonly Func EFPropertyModifiedByIp = - entity => EF.Property(entity, ModifiedByIp); - public static readonly string ModifiedByIp = nameof(ModifiedByIp); - - public static readonly Func EFPropertyCreatedByUserId = - entity => EF.Property(entity, CreatedByUserId); - public static readonly string CreatedByUserId = nameof(CreatedByUserId); - - public static readonly Func EFPropertyModifiedByUserId = - entity => EF.Property(entity, ModifiedByUserId); - public static readonly string ModifiedByUserId = nameof(ModifiedByUserId); - - public static readonly Func EFPropertyCreatedDateTime = - entity => EF.Property(entity, CreatedDateTime); - public static readonly string CreatedDateTime = nameof(CreatedDateTime); + if (changeTracker == null) + { + throw new ArgumentNullException(nameof(changeTracker)); + } + + if (props == null) + { + return; + } + + var modifiedEntries = changeTracker.Entries() + .Where(x => x.State == EntityState.Modified); + foreach (var modifiedEntry in modifiedEntries) + { + modifiedEntry.SetModifiedShadowProperties(props); + } - public static readonly Func EFPropertyModifiedDateTime = - entity => EF.Property(entity, ModifiedDateTime); - public static readonly string ModifiedDateTime = nameof(ModifiedDateTime); - - public static void AddAuditableShadowProperties(this ModelBuilder modelBuilder) + var addedEntries = changeTracker.Entries() + .Where(x => x.State == EntityState.Added); + foreach (var addedEntry in addedEntries) { - foreach (var entityType in modelBuilder.Model - .GetEntityTypes() - .Where(e => typeof(IAuditableEntity).IsAssignableFrom(e.ClrType))) - { - modelBuilder.Entity(entityType.ClrType) - .Property(CreatedByBrowserName).HasMaxLength(1000); - modelBuilder.Entity(entityType.ClrType) - .Property(ModifiedByBrowserName).HasMaxLength(1000); + addedEntry.SetAddedShadowProperties(props); + } + } - modelBuilder.Entity(entityType.ClrType) - .Property(CreatedByIp).HasMaxLength(255); - modelBuilder.Entity(entityType.ClrType) - .Property(ModifiedByIp).HasMaxLength(255); - - modelBuilder.Entity(entityType.ClrType) - .Property(CreatedByUserId); - modelBuilder.Entity(entityType.ClrType) - .Property(ModifiedByUserId); - - modelBuilder.Entity(entityType.ClrType) - .Property(CreatedDateTime); - modelBuilder.Entity(entityType.ClrType) - .Property(ModifiedDateTime); - } - } - - /// - /// More info: http://www.dotnettips.info/post/2507 - /// - public static void SetAuditableEntityPropertyValues( - this ChangeTracker changeTracker, - AppShadowProperties props) - { - if (props == null) - { - return; - } - - var modifiedEntries = changeTracker.Entries() - .Where(x => x.State == EntityState.Modified); - foreach (var modifiedEntry in modifiedEntries) - { - modifiedEntry.SetModifiedShadowProperties(props); - } - - var addedEntries = changeTracker.Entries() - .Where(x => x.State == EntityState.Added); - foreach (var addedEntry in addedEntries) - { - addedEntry.SetAddedShadowProperties(props); - } - } - - public static void SetAddedShadowProperties(this EntityEntry addedEntry, AppShadowProperties props) - { - if (props == null) - { - return; - } - - addedEntry.Property(CreatedDateTime).CurrentValue = props.Now; - if (!string.IsNullOrWhiteSpace(props.UserAgent)) addedEntry.Property(CreatedByBrowserName).CurrentValue = props.UserAgent; - if (!string.IsNullOrWhiteSpace(props.UserIp)) addedEntry.Property(CreatedByIp).CurrentValue = props.UserIp; - if (props.UserId.HasValue) addedEntry.Property(CreatedByUserId).CurrentValue = props.UserId; - } - - public static void SetAddedShadowProperties(this EntityEntry addedEntry, AppShadowProperties props) - { - if (props == null) - { - return; - } + public static void SetAddedShadowProperties(this EntityEntry addedEntry, + AppShadowProperties props) + { + if (addedEntry == null) + { + throw new ArgumentNullException(nameof(addedEntry)); + } - addedEntry.Property(CreatedDateTime).CurrentValue = props.Now; - if (!string.IsNullOrWhiteSpace(props.UserAgent)) addedEntry.Property(CreatedByBrowserName).CurrentValue = props.UserAgent; - if (!string.IsNullOrWhiteSpace(props.UserIp)) addedEntry.Property(CreatedByIp).CurrentValue = props.UserIp; - if (props.UserId.HasValue) addedEntry.Property(CreatedByUserId).CurrentValue = props.UserId; + if (props == null) + { + return; } - - public static void SetModifiedShadowProperties(this EntityEntry modifiedEntry, AppShadowProperties props) + + addedEntry.Property(CreatedDateTime).CurrentValue = props.Now; + if (!string.IsNullOrWhiteSpace(props.UserAgent)) { - if (props == null) - { - return; - } + addedEntry.Property(CreatedByBrowserName).CurrentValue = props.UserAgent; + } - modifiedEntry.Property(ModifiedDateTime).CurrentValue = props.Now; - if (!string.IsNullOrWhiteSpace(props.UserAgent)) modifiedEntry.Property(ModifiedByBrowserName).CurrentValue = props.UserAgent; - if (!string.IsNullOrWhiteSpace(props.UserIp)) modifiedEntry.Property(ModifiedByIp).CurrentValue = props.UserIp; - if (props.UserId.HasValue) modifiedEntry.Property(ModifiedByUserId).CurrentValue = props.UserId; - } - - public static AppShadowProperties GetShadowProperties(this IHttpContextAccessor httpContextAccessor) + if (!string.IsNullOrWhiteSpace(props.UserIp)) { - if (httpContextAccessor == null) - { - return null; - } - - var httpContext = httpContextAccessor?.HttpContext; - return new AppShadowProperties - { - UserAgent = httpContext?.Request?.Headers["User-Agent"].ToString(), - UserIp = httpContext?.Connection?.RemoteIpAddress?.ToString(), - Now = DateTime.UtcNow, - UserId = getUserId(httpContext) - }; + addedEntry.Property(CreatedByIp).CurrentValue = props.UserIp; } - private static int? getUserId(HttpContext httpContext) + if (props.UserId.HasValue) { - int? userId = null; - var userIdValue = httpContext?.User?.Identity?.GetUserId(); - if (!string.IsNullOrWhiteSpace(userIdValue)) - { - userId = int.Parse(userIdValue); - } - return userId; + addedEntry.Property(CreatedByUserId).CurrentValue = props.UserId; } } + + public static void SetAddedShadowProperties(this EntityEntry addedEntry, AppShadowProperties props) + { + if (addedEntry == null) + { + throw new ArgumentNullException(nameof(addedEntry)); + } + + if (props == null) + { + return; + } + + addedEntry.Property(CreatedDateTime).CurrentValue = props.Now; + if (!string.IsNullOrWhiteSpace(props.UserAgent)) + { + addedEntry.Property(CreatedByBrowserName).CurrentValue = props.UserAgent; + } + + if (!string.IsNullOrWhiteSpace(props.UserIp)) + { + addedEntry.Property(CreatedByIp).CurrentValue = props.UserIp; + } + + if (props.UserId.HasValue) + { + addedEntry.Property(CreatedByUserId).CurrentValue = props.UserId; + } + } + + public static void SetModifiedShadowProperties(this EntityEntry modifiedEntry, + AppShadowProperties props) + { + if (modifiedEntry == null) + { + throw new ArgumentNullException(nameof(modifiedEntry)); + } + + if (props == null) + { + return; + } + + modifiedEntry.Property(ModifiedDateTime).CurrentValue = props.Now; + if (!string.IsNullOrWhiteSpace(props.UserAgent)) + { + modifiedEntry.Property(ModifiedByBrowserName).CurrentValue = props.UserAgent; + } + + if (!string.IsNullOrWhiteSpace(props.UserIp)) + { + modifiedEntry.Property(ModifiedByIp).CurrentValue = props.UserIp; + } + + if (props.UserId.HasValue) + { + modifiedEntry.Property(ModifiedByUserId).CurrentValue = props.UserId; + } + } + + public static AppShadowProperties GetShadowProperties(this IHttpContextAccessor httpContextAccessor) + { + if (httpContextAccessor == null) + { + return null; + } + + var httpContext = httpContextAccessor.HttpContext; + return new AppShadowProperties + { + UserAgent = httpContext?.Request?.Headers["User-Agent"].ToString(), + UserIp = httpContext?.Connection?.RemoteIpAddress?.ToString(), + Now = DateTime.UtcNow, + UserId = GetUserId(httpContext) + }; + } + + private static int? GetUserId(HttpContext httpContext) + { + int? userId = null; + var userIdValue = httpContext?.User?.Identity?.GetUserId(); + if (!string.IsNullOrWhiteSpace(userIdValue)) + { + userId = int.Parse(userIdValue, NumberStyles.Number, CultureInfo.InvariantCulture); + } + + return userId; + } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/IAuditableEntity.cs b/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/IAuditableEntity.cs index 7195057..3c2dd9f 100644 --- a/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/IAuditableEntity.cs +++ b/src/ASPNETCoreIdentitySample.Entities/AuditableEntity/IAuditableEntity.cs @@ -1,12 +1,11 @@ -namespace ASPNETCoreIdentitySample.Entities.AuditableEntity +namespace ASPNETCoreIdentitySample.Entities.AuditableEntity; + +/// +/// It's a marker interface, in order to make our entities audit-able. +/// Every entity you mark with this interface, will save audit info to the database. +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// +public interface IAuditableEntity { - /// - /// It's a marker interface, in order to make our entities audit-able. - /// Every entity you mark with this interface, will save audit info to the database. - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// - public interface IAuditableEntity - { - } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Category.cs b/src/ASPNETCoreIdentitySample.Entities/Category.cs index f4446e4..0fb21aa 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Category.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Category.cs @@ -1,21 +1,19 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities.AuditableEntity; +using ASPNETCoreIdentitySample.Entities.AuditableEntity; -namespace ASPNETCoreIdentitySample.Entities +namespace ASPNETCoreIdentitySample.Entities; + +public class Category : IAuditableEntity { - public class Category : IAuditableEntity - { - public int Id { get; set; } + public int Id { get; set; } - public Category() - { - Products = new HashSet(); - } + public Category() + { + Products = new HashSet(); + } - public string Name { get; set; } + public string Name { get; set; } - public string Title { get; set; } + public string Title { get; set; } - public virtual ICollection Products { get; set; } - } + public virtual ICollection Products { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/AppDataProtectionKey.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/AppDataProtectionKey.cs index acf59ab..be0ddab 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/AppDataProtectionKey.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/AppDataProtectionKey.cs @@ -1,9 +1,8 @@ -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +public class AppDataProtectionKey { - public class AppDataProtectionKey - { - public int Id { get; set; } - public string FriendlyName { get; set; } - public string XmlData { get; set; } - } + public int Id { get; set; } + public string FriendlyName { get; set; } + public string XmlData { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/AppLogItem.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/AppLogItem.cs index adc0959..4d2366d 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/AppLogItem.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/AppLogItem.cs @@ -1,24 +1,22 @@ -using System; -using ASPNETCoreIdentitySample.Entities.AuditableEntity; +using ASPNETCoreIdentitySample.Entities.AuditableEntity; -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +public class AppLogItem : IAuditableEntity { - public class AppLogItem : IAuditableEntity - { - public int Id { set; get; } + public int Id { set; get; } - public DateTime? CreatedDateTime { get; set; } + public DateTime? CreatedDateTime { get; set; } - public int EventId { get; set; } + public int EventId { get; set; } - public string Url { get; set; } + public string Url { get; set; } - public string LogLevel { get; set; } + public string LogLevel { get; set; } - public string Logger { get; set; } + public string Logger { get; set; } - public string Message { get; set; } + public string Message { get; set; } - public string StateJson { get; set; } - } + public string StateJson { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/AppSqlCache.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/AppSqlCache.cs index 8a6b214..d05b606 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/AppSqlCache.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/AppSqlCache.cs @@ -1,19 +1,16 @@ -using System; +namespace ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.Entities.Identity +/// +/// For Microsoft.Extensions.Caching.SqlServer +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// plus http://www.dntips.ir/post/2581 +/// +public class AppSqlCache { - /// - /// For Microsoft.Extensions.Caching.SqlServer - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// plus http://www.dotnettips.info/post/2581 - /// - public class AppSqlCache - { - public string Id { get; set; } - public byte[] Value { get; set; } - public DateTimeOffset ExpiresAtTime { get; set; } - public long? SlidingExpirationInSeconds { get; set; } - public DateTimeOffset? AbsoluteExpiration { get; set; } - } + public string Id { get; set; } + public byte[] Value { get; set; } + public DateTimeOffset ExpiresAtTime { get; set; } + public long? SlidingExpirationInSeconds { get; set; } + public DateTimeOffset? AbsoluteExpiration { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/Role.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/Role.cs index 94dace0..5423ed7 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/Role.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/Role.cs @@ -1,35 +1,33 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities.AuditableEntity; +using ASPNETCoreIdentitySample.Entities.AuditableEntity; using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +/// +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// +public class Role : IdentityRole, IAuditableEntity { - /// - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// - public class Role : IdentityRole, IAuditableEntity + public Role() { - public Role() - { - } + } - public Role(string name) - : this() - { - Name = name; - } + public Role(string name) + : this() + { + Name = name; + } - public Role(string name, string description) - : this(name) - { - Description = description; - } + public Role(string name, string description) + : this(name) + { + Description = description; + } - public string Description { get; set; } + public string Description { get; set; } - public virtual ICollection Users { get; set; } + public virtual ICollection Users { get; set; } - public virtual ICollection Claims { get; set; } - } + public virtual ICollection Claims { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/RoleClaim.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/RoleClaim.cs index 8d452ab..194f81d 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/RoleClaim.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/RoleClaim.cs @@ -1,14 +1,13 @@ using ASPNETCoreIdentitySample.Entities.AuditableEntity; using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +/// +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// +public class RoleClaim : IdentityRoleClaim, IAuditableEntity { - /// - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// - public class RoleClaim : IdentityRoleClaim, IAuditableEntity - { - public virtual Role Role { get; set; } - } + public virtual Role Role { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/User.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/User.cs index 9f77a6c..9579f09 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/User.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/User.cs @@ -1,64 +1,56 @@ using ASPNETCoreIdentitySample.Entities.AuditableEntity; using Microsoft.AspNetCore.Identity; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.ComponentModel.DataAnnotations; -using System; -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +/// +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// plus http://www.dntips.ir/post/2559 +/// +public class User : IdentityUser, IAuditableEntity { - /// - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// plus http://www.dotnettips.info/post/2559 - /// - public class User : IdentityUser, IAuditableEntity + public User() { - public User() - { - UserUsedPasswords = new HashSet(); - UserTokens = new HashSet(); - } + UserUsedPasswords = new HashSet(); + UserTokens = new HashSet(); + } - [StringLength(450)] - public string FirstName { get; set; } + [StringLength(450)] public string FirstName { get; set; } - [StringLength(450)] - public string LastName { get; set; } + [StringLength(450)] public string LastName { get; set; } - [NotMapped] - public string DisplayName + [NotMapped] + public string DisplayName + { + get { - get - { - var displayName = $"{FirstName} {LastName}"; - return string.IsNullOrWhiteSpace(displayName) ? UserName : displayName; - } + var displayName = $"{FirstName} {LastName}"; + return string.IsNullOrWhiteSpace(displayName) ? UserName : displayName; } + } - [StringLength(450)] - public string PhotoFileName { get; set; } + [StringLength(450)] public string PhotoFileName { get; set; } - public DateTime? BirthDate { get; set; } + public DateTime? BirthDate { get; set; } - public DateTime? CreatedDateTime { get; set; } + public DateTime? CreatedDateTime { get; set; } - public DateTime? LastVisitDateTime { get; set; } + public DateTime? LastVisitDateTime { get; set; } - public bool IsEmailPublic { get; set; } + public bool IsEmailPublic { get; set; } - public string Location { set; get; } + public string Location { set; get; } - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } = true; - public virtual ICollection UserUsedPasswords { get; set; } + public virtual ICollection UserUsedPasswords { get; set; } - public virtual ICollection UserTokens { get; set; } + public virtual ICollection UserTokens { get; set; } - public virtual ICollection Roles { get; set; } + public virtual ICollection Roles { get; set; } - public virtual ICollection Logins { get; set; } + public virtual ICollection Logins { get; set; } - public virtual ICollection Claims { get; set; } - } + public virtual ICollection Claims { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/UserClaim.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/UserClaim.cs index fd81dc1..55dc151 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/UserClaim.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/UserClaim.cs @@ -1,14 +1,13 @@ using ASPNETCoreIdentitySample.Entities.AuditableEntity; using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +/// +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// +public class UserClaim : IdentityUserClaim, IAuditableEntity { - /// - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// - public class UserClaim : IdentityUserClaim, IAuditableEntity - { - public virtual User User { get; set; } - } + public virtual User User { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/UserLogin.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/UserLogin.cs index 277053e..a34f40f 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/UserLogin.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/UserLogin.cs @@ -1,14 +1,13 @@ using ASPNETCoreIdentitySample.Entities.AuditableEntity; using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +/// +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// +public class UserLogin : IdentityUserLogin, IAuditableEntity { - /// - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// - public class UserLogin : IdentityUserLogin, IAuditableEntity - { - public virtual User User { get; set; } - } + public virtual User User { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/UserRole.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/UserRole.cs index 7bff391..0ed694d 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/UserRole.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/UserRole.cs @@ -1,16 +1,15 @@ using ASPNETCoreIdentitySample.Entities.AuditableEntity; using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +/// +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// +public class UserRole : IdentityUserRole, IAuditableEntity { - /// - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// - public class UserRole : IdentityUserRole, IAuditableEntity - { - public virtual User User { get; set; } + public virtual User User { get; set; } - public virtual Role Role { get; set; } - } + public virtual Role Role { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/UserToken.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/UserToken.cs index 394ab43..6e6f96f 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/UserToken.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/UserToken.cs @@ -1,14 +1,13 @@ using ASPNETCoreIdentitySample.Entities.AuditableEntity; using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +/// +/// More info: http://www.dntips.ir/post/2577 +/// and http://www.dntips.ir/post/2578 +/// +public class UserToken : IdentityUserToken, IAuditableEntity { - /// - /// More info: http://www.dotnettips.info/post/2577 - /// and http://www.dotnettips.info/post/2578 - /// - public class UserToken : IdentityUserToken, IAuditableEntity - { - public virtual User User { get; set; } - } + public virtual User User { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Identity/UserUsedPassword.cs b/src/ASPNETCoreIdentitySample.Entities/Identity/UserUsedPassword.cs index af2b2c8..0953392 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Identity/UserUsedPassword.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Identity/UserUsedPassword.cs @@ -1,14 +1,13 @@ using ASPNETCoreIdentitySample.Entities.AuditableEntity; -namespace ASPNETCoreIdentitySample.Entities.Identity +namespace ASPNETCoreIdentitySample.Entities.Identity; + +public class UserUsedPassword : IAuditableEntity { - public class UserUsedPassword : IAuditableEntity - { - public int Id { get; set; } + public int Id { get; set; } - public string HashedPassword { get; set; } + public string HashedPassword { get; set; } - public virtual User User { get; set; } - public int UserId { get; set; } - } + public virtual User User { get; set; } + public int UserId { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Entities/Product.cs b/src/ASPNETCoreIdentitySample.Entities/Product.cs index cb7996a..f449b4e 100644 --- a/src/ASPNETCoreIdentitySample.Entities/Product.cs +++ b/src/ASPNETCoreIdentitySample.Entities/Product.cs @@ -1,16 +1,15 @@ using ASPNETCoreIdentitySample.Entities.AuditableEntity; -namespace ASPNETCoreIdentitySample.Entities +namespace ASPNETCoreIdentitySample.Entities; + +public class Product : IAuditableEntity { - public class Product : IAuditableEntity - { - public int Id { get; set; } + public int Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } - public decimal Price { get; set; } + public decimal Price { get; set; } - public virtual Category Category { get; set; } - public int CategoryId { get; set; } - } + public virtual Category Category { get; set; } + public int CategoryId { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.IocConfig/ASPNETCoreIdentitySample.IocConfig.csproj b/src/ASPNETCoreIdentitySample.IocConfig/ASPNETCoreIdentitySample.IocConfig.csproj index 74b1839..e05c60e 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/ASPNETCoreIdentitySample.IocConfig.csproj +++ b/src/ASPNETCoreIdentitySample.IocConfig/ASPNETCoreIdentitySample.IocConfig.csproj @@ -1,7 +1,6 @@ - net5.0 - RCS1090 + net6.0 @@ -16,9 +15,9 @@ - - - - + + + + diff --git a/src/ASPNETCoreIdentitySample.IocConfig/AddCustomServicesExtensions.cs b/src/ASPNETCoreIdentitySample.IocConfig/AddCustomServicesExtensions.cs index e8272ab..9efa857 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/AddCustomServicesExtensions.cs +++ b/src/ASPNETCoreIdentitySample.IocConfig/AddCustomServicesExtensions.cs @@ -10,58 +10,57 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -namespace ASPNETCoreIdentitySample.IocConfig +namespace ASPNETCoreIdentitySample.IocConfig; + +public static class AddCustomServicesExtensions { - public static class AddCustomServicesExtensions + public static IServiceCollection AddCustomServices(this IServiceCollection services) { - public static IServiceCollection AddCustomServices(this IServiceCollection services) - { - services.AddSingleton(); - services.AddScoped(provider => - provider.GetRequiredService()?.HttpContext?.User ?? ClaimsPrincipal.Current); + services.AddSingleton(); + services.AddScoped(provider => + provider.GetRequiredService()?.HttpContext?.User ?? ClaimsPrincipal.Current); - services.AddScoped(); + services.AddScoped(); - services.AddScoped(); - services.AddScoped, CustomSecurityStampValidator>(); + services.AddScoped(); + services.AddScoped, CustomSecurityStampValidator>(); - services.AddScoped, CustomPasswordValidator>(); - services.AddScoped, CustomPasswordValidator>(); + services.AddScoped, CustomPasswordValidator>(); + services.AddScoped, CustomPasswordValidator>(); - services.AddScoped, CustomUserValidator>(); - services.AddScoped, CustomUserValidator>(); + services.AddScoped, CustomUserValidator>(); + services.AddScoped, CustomUserValidator>(); - services.AddScoped, ApplicationClaimsPrincipalFactory>(); - services.AddScoped, ApplicationClaimsPrincipalFactory>(); + services.AddScoped, ApplicationClaimsPrincipalFactory>(); + services.AddScoped, ApplicationClaimsPrincipalFactory>(); - services.AddScoped(); + services.AddScoped(); - services.AddScoped(); - services.AddScoped, ApplicationUserStore>(); + services.AddScoped(); + services.AddScoped, ApplicationUserStore>(); - services.AddScoped(); - services.AddScoped, ApplicationUserManager>(); + services.AddScoped(); + services.AddScoped, ApplicationUserManager>(); - services.AddScoped(); - services.AddScoped, ApplicationRoleManager>(); + services.AddScoped(); + services.AddScoped, ApplicationRoleManager>(); - services.AddScoped(); - services.AddScoped, ApplicationSignInManager>(); + services.AddScoped(); + services.AddScoped, ApplicationSignInManager>(); - services.AddScoped(); - services.AddScoped, ApplicationRoleStore>(); + services.AddScoped(); + services.AddScoped, ApplicationRoleStore>(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - return services; - } + return services; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.IocConfig/AddDynamicPermissionsExtensions.cs b/src/ASPNETCoreIdentitySample.IocConfig/AddDynamicPermissionsExtensions.cs index fb97bf3..5980140 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/AddDynamicPermissionsExtensions.cs +++ b/src/ASPNETCoreIdentitySample.IocConfig/AddDynamicPermissionsExtensions.cs @@ -2,25 +2,24 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; -namespace ASPNETCoreIdentitySample.IocConfig +namespace ASPNETCoreIdentitySample.IocConfig; + +public static class AddDynamicPermissionsExtensions { - public static class AddDynamicPermissionsExtensions + public static IServiceCollection AddDynamicPermissions(this IServiceCollection services) { - public static IServiceCollection AddDynamicPermissions(this IServiceCollection services) + services.AddScoped(); + services.AddAuthorization(opts => { - services.AddScoped(); - services.AddAuthorization(opts => - { - opts.AddPolicy( - name: ConstantPolicies.DynamicPermission, - configurePolicy: policy => - { - policy.RequireAuthenticatedUser(); - policy.Requirements.Add(new DynamicPermissionRequirement()); - }); - }); + opts.AddPolicy( + name: ConstantPolicies.DynamicPermission, + configurePolicy: policy => + { + policy.RequireAuthenticatedUser(); + policy.Requirements.Add(new DynamicPermissionRequirement()); + }); + }); - return services; - } + return services; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.IocConfig/AddIdentityOptionsExtensions.cs b/src/ASPNETCoreIdentitySample.IocConfig/AddIdentityOptionsExtensions.cs index f94d21c..e65ddf3 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/AddIdentityOptionsExtensions.cs +++ b/src/ASPNETCoreIdentitySample.IocConfig/AddIdentityOptionsExtensions.cs @@ -1,5 +1,3 @@ -using System; -using System.Threading.Tasks; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; @@ -8,123 +6,128 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -namespace ASPNETCoreIdentitySample.IocConfig +namespace ASPNETCoreIdentitySample.IocConfig; + +public static class AddIdentityOptionsExtensions { - public static class AddIdentityOptionsExtensions - { - public const string EmailConfirmationTokenProviderName = "ConfirmEmail"; + 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) { - if (siteSettings == null) throw new ArgumentNullException(nameof(siteSettings)); + throw new ArgumentNullException(nameof(siteSettings)); + } - services.addConfirmEmailDataProtectorTokenOptions(siteSettings); - services.AddIdentity(identityOptions => + services.AddConfirmEmailDataProtectorTokenOptions(siteSettings); + services.AddIdentity(identityOptions => { - setPasswordOptions(identityOptions.Password, siteSettings); - setSignInOptions(identityOptions.SignIn, siteSettings); - setUserOptions(identityOptions.User); - setLockoutOptions(identityOptions.Lockout, siteSettings); + SetPasswordOptions(identityOptions.Password, siteSettings); + SetSignInOptions(identityOptions.SignIn, siteSettings); + SetUserOptions(identityOptions.User); + SetLockoutOptions(identityOptions.Lockout, siteSettings); }).AddUserStore() - .AddUserManager() - .AddRoleStore() - .AddRoleManager() - .AddSignInManager() - .AddErrorDescriber() - // You **cannot** use .AddEntityFrameworkStores() when you customize everything - //.AddEntityFrameworkStores() - .AddDefaultTokenProviders() - .AddTokenProvider>(EmailConfirmationTokenProviderName); - - services.ConfigureApplicationCookie(identityOptionsCookies => - { - var provider = services.BuildServiceProvider(); - setApplicationCookieOptions(provider, identityOptionsCookies, siteSettings); - }); + .AddUserManager() + .AddRoleStore() + .AddRoleManager() + .AddSignInManager() + .AddErrorDescriber() + // You **cannot** use .AddEntityFrameworkStores() when you customize everything + //.AddEntityFrameworkStores() + .AddDefaultTokenProviders() + .AddTokenProvider>(EmailConfirmationTokenProviderName); + + services.ConfigureApplicationCookie(identityOptionsCookies => + { + var provider = services.BuildServiceProvider(); + SetApplicationCookieOptions(provider, identityOptionsCookies, siteSettings); + }); - services.enableImmediateLogout(); + services.EnableImmediateLogout(); - return services; - } + return services; + } - private static void addConfirmEmailDataProtectorTokenOptions(this IServiceCollection services, SiteSettings siteSettings) + private static void AddConfirmEmailDataProtectorTokenOptions(this IServiceCollection services, + SiteSettings siteSettings) + { + services.Configure(options => { - services.Configure(options => - { - options.Tokens.EmailConfirmationTokenProvider = EmailConfirmationTokenProviderName; - }); + options.Tokens.EmailConfirmationTokenProvider = EmailConfirmationTokenProviderName; + }); - services.Configure(options => - { - options.TokenLifespan = siteSettings.EmailConfirmationTokenProviderLifespan; - }); - } - - 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. - - //var newId = new ClaimsIdentity(); - //newId.AddClaim(new Claim("PreviousName", principalContext.CurrentPrincipal.Identity.Name)); - //principalContext.NewPrincipal.AddIdentity(newId); - - return Task.CompletedTask; - }; - }); - } + options.TokenLifespan = siteSettings.EmailConfirmationTokenProviderLifespan; + }); + } - private static void setApplicationCookieOptions(IServiceProvider provider, CookieAuthenticationOptions identityOptionsCookies, SiteSettings siteSettings) + private static void EnableImmediateLogout(this IServiceCollection services) + { + services.Configure(options => { - 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 - - identityOptionsCookies.ExpireTimeSpan = siteSettings.CookieOptions.ExpireTimeSpan; - identityOptionsCookies.SlidingExpiration = siteSettings.CookieOptions.SlidingExpiration; - identityOptionsCookies.LoginPath = siteSettings.CookieOptions.LoginPath; - identityOptionsCookies.LogoutPath = siteSettings.CookieOptions.LogoutPath; - identityOptionsCookies.AccessDeniedPath = siteSettings.CookieOptions.AccessDeniedPath; - - if (siteSettings.CookieOptions.UseDistributedCacheTicketStore) + // enables immediate logout, after updating the user's stat. + options.ValidationInterval = TimeSpan.Zero; + options.OnRefreshingPrincipal = principalContext => { - // To manage large identity cookies - identityOptionsCookies.SessionStore = provider.GetRequiredService(); - } - } + // Invoked when the default security stamp validator replaces the user's ClaimsPrincipal in the cookie. - private static void setLockoutOptions(LockoutOptions identityOptionsLockout, SiteSettings siteSettings) - { - identityOptionsLockout.AllowedForNewUsers = siteSettings.LockoutOptions.AllowedForNewUsers; - identityOptionsLockout.DefaultLockoutTimeSpan = siteSettings.LockoutOptions.DefaultLockoutTimeSpan; - identityOptionsLockout.MaxFailedAccessAttempts = siteSettings.LockoutOptions.MaxFailedAccessAttempts; - } + //var newId = new ClaimsIdentity() + //newId.AddClaim(new Claim("PreviousName", principalContext.CurrentPrincipal.Identity.Name)) + //principalContext.NewPrincipal.AddIdentity(newId) - private static void setPasswordOptions(PasswordOptions identityOptionsPassword, SiteSettings siteSettings) - { - identityOptionsPassword.RequireDigit = siteSettings.PasswordOptions.RequireDigit; - identityOptionsPassword.RequireLowercase = siteSettings.PasswordOptions.RequireLowercase; - identityOptionsPassword.RequireNonAlphanumeric = siteSettings.PasswordOptions.RequireNonAlphanumeric; - identityOptionsPassword.RequireUppercase = siteSettings.PasswordOptions.RequireUppercase; - identityOptionsPassword.RequiredLength = siteSettings.PasswordOptions.RequiredLength; - } + return Task.CompletedTask; + }; + }); + } - private static void setSignInOptions(SignInOptions identityOptionsSignIn, SiteSettings siteSettings) + private static void SetApplicationCookieOptions(IServiceProvider provider, + 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 + + identityOptionsCookies.ExpireTimeSpan = siteSettings.CookieOptions.ExpireTimeSpan; + identityOptionsCookies.SlidingExpiration = siteSettings.CookieOptions.SlidingExpiration; + identityOptionsCookies.LoginPath = siteSettings.CookieOptions.LoginPath; + identityOptionsCookies.LogoutPath = siteSettings.CookieOptions.LogoutPath; + identityOptionsCookies.AccessDeniedPath = siteSettings.CookieOptions.AccessDeniedPath; + + if (siteSettings.CookieOptions.UseDistributedCacheTicketStore) { - identityOptionsSignIn.RequireConfirmedEmail = siteSettings.EnableEmailConfirmation; + // To manage large identity cookies + identityOptionsCookies.SessionStore = provider.GetRequiredService(); } + } - private static void setUserOptions(UserOptions identityOptionsUser) - { - identityOptionsUser.RequireUniqueEmail = true; - } + private static void SetLockoutOptions(LockoutOptions identityOptionsLockout, SiteSettings siteSettings) + { + identityOptionsLockout.AllowedForNewUsers = siteSettings.LockoutOptions.AllowedForNewUsers; + identityOptionsLockout.DefaultLockoutTimeSpan = siteSettings.LockoutOptions.DefaultLockoutTimeSpan; + identityOptionsLockout.MaxFailedAccessAttempts = siteSettings.LockoutOptions.MaxFailedAccessAttempts; + } + + private static void SetPasswordOptions(PasswordOptions identityOptionsPassword, SiteSettings siteSettings) + { + identityOptionsPassword.RequireDigit = siteSettings.PasswordOptions.RequireDigit; + identityOptionsPassword.RequireLowercase = siteSettings.PasswordOptions.RequireLowercase; + identityOptionsPassword.RequireNonAlphanumeric = siteSettings.PasswordOptions.RequireNonAlphanumeric; + identityOptionsPassword.RequireUppercase = siteSettings.PasswordOptions.RequireUppercase; + identityOptionsPassword.RequiredLength = siteSettings.PasswordOptions.RequiredLength; + } + + private static void SetSignInOptions(SignInOptions identityOptionsSignIn, SiteSettings siteSettings) + { + identityOptionsSignIn.RequireConfirmedEmail = siteSettings.EnableEmailConfirmation; + } + + private static void SetUserOptions(UserOptions identityOptionsUser) + { + identityOptionsUser.RequireUniqueEmail = true; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.IocConfig/CustomDataProtectionExtensions.cs b/src/ASPNETCoreIdentitySample.IocConfig/CustomDataProtectionExtensions.cs index 4c97b3b..085ef97 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/CustomDataProtectionExtensions.cs +++ b/src/ASPNETCoreIdentitySample.IocConfig/CustomDataProtectionExtensions.cs @@ -1,5 +1,3 @@ -using System; -using System.IO; using System.Security.Cryptography.X509Certificates; using ASPNETCoreIdentitySample.Common.WebToolkit; using ASPNETCoreIdentitySample.Services.Identity; @@ -11,57 +9,65 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -namespace ASPNETCoreIdentitySample.IocConfig +namespace ASPNETCoreIdentitySample.IocConfig; + +public static class CustomDataProtectionExtensions { - 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) + { + throw new ArgumentNullException(nameof(siteSettings)); + } + + services.AddSingleton(); + services.AddSingleton>(serviceProvider => { - services.AddSingleton(); - services.AddSingleton>(serviceProvider => + return new ConfigureOptions(options => { - return new ConfigureOptions(options => - { - serviceProvider.RunScopedService(xmlRepository => - options.XmlRepository = xmlRepository); - }); + serviceProvider.RunScopedService(xmlRepository => + options.XmlRepository = xmlRepository); }); + }); - //var certificate = loadCertificateFromFile(siteSettings); - services - .AddDataProtection() - .SetDefaultKeyLifetime(siteSettings.DataProtectionOptions.DataProtectionKeyLifetime) - .SetApplicationName(siteSettings.DataProtectionOptions.ApplicationName); - //.ProtectKeysWithCertificate(certificate); + //var certificate = LoadCertificateFromFile(siteSettings) + services + .AddDataProtection() + .SetDefaultKeyLifetime(siteSettings.DataProtectionOptions.DataProtectionKeyLifetime) + .SetApplicationName(siteSettings.DataProtectionOptions.ApplicationName); + //.ProtectKeysWithCertificate(certificate) - return services; - } + return services; + } - private static X509Certificate2 loadCertificateFromFile(SiteSettings siteSettings) - { - // NOTE: - // You should check out the identity of your application pool and make sure - // that the `Load user profile` option is turned on, otherwise the crypto susbsystem won't work. + private static X509Certificate2 LoadCertificateFromFile(SiteSettings siteSettings) + { + // NOTE: + // You should check out the identity of your application pool and make sure + // that the `Load user profile` option is turned on, otherwise the crypto susbsystem won't work. - var certificate = siteSettings.DataProtectionX509Certificate; - var fileName = Path.Combine(ServerInfo.GetAppDataFolderPath(), certificate.FileName); + var certificate = siteSettings.DataProtectionX509Certificate; + var fileName = Path.Combine(ServerInfo.GetAppDataFolderPath(), certificate.FileName); - // For decryption the certificate must be in the certificate store. It's a limitation of how EncryptedXml works. - using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + // For decryption the certificate must be in the certificate store. It's a limitation of how EncryptedXml works. + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadWrite); + using (var x509Certificate2 = + new X509Certificate2(fileName, certificate.Password, X509KeyStorageFlags.Exportable)) { - store.Open(OpenFlags.ReadWrite); - store.Add(new X509Certificate2(fileName, certificate.Password, X509KeyStorageFlags.Exportable)); + store.Add(x509Certificate2); } - - var cert = new X509Certificate2( - fileName, - certificate.Password, - keyStorageFlags: X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet - | X509KeyStorageFlags.Exportable); - // TODO: If you are getting `Keyset does not exist`, run `wwwroot\App_Data\make-cert.cmd` again. - Console.WriteLine($"cert private key: {cert.PrivateKey}"); - return cert; } + + var cert = new X509Certificate2( + 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.IocConfig/CustomTicketStoreExtensions.cs b/src/ASPNETCoreIdentitySample.IocConfig/CustomTicketStoreExtensions.cs index a5b4f3e..758119e 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/CustomTicketStoreExtensions.cs +++ b/src/ASPNETCoreIdentitySample.IocConfig/CustomTicketStoreExtensions.cs @@ -1,55 +1,54 @@ -using System; using ASPNETCoreIdentitySample.DataLayer.MSSQL; using ASPNETCoreIdentitySample.Services.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.DependencyInjection; -namespace ASPNETCoreIdentitySample.IocConfig +namespace ASPNETCoreIdentitySample.IocConfig; + +public static class CustomTicketStoreExtensions { - public static class CustomTicketStoreExtensions + public static IServiceCollection AddCustomTicketStore( + this IServiceCollection services, SiteSettings siteSettings) { - public static IServiceCollection AddCustomTicketStore( - this IServiceCollection services, SiteSettings siteSettings) + if (siteSettings == null) { - // To manage large identity cookies - var cookieOptions = siteSettings.CookieOptions; - if (!cookieOptions.UseDistributedCacheTicketStore) - { - return services; - } - - switch (siteSettings.ActiveDatabase) - { - case ActiveDatabase.InMemoryDatabase: - services.AddMemoryCache(); - services.AddScoped(); - break; + throw new ArgumentNullException(nameof(siteSettings)); + } - case ActiveDatabase.LocalDb: - case ActiveDatabase.SqlServer: - services.AddDistributedSqlServerCache(options => - { - var cacheOptions = cookieOptions.DistributedSqlServerCacheOptions; - options.ConnectionString = string.IsNullOrWhiteSpace(cacheOptions.ConnectionString) ? - siteSettings.GetMsSqlDbConnectionString() : - cacheOptions.ConnectionString; - options.SchemaName = cacheOptions.SchemaName; - options.TableName = cacheOptions.TableName; - }); - services.AddScoped(); - break; + // To manage large identity cookies + var cookieOptions = siteSettings.CookieOptions; + if (!cookieOptions.UseDistributedCacheTicketStore) + { + return services; + } - case ActiveDatabase.SQLite: //TODO: - services.AddMemoryCache(); - services.AddScoped(); - break; + switch (siteSettings.ActiveDatabase) + { + case ActiveDatabase.SQLite: //TODO: + case ActiveDatabase.InMemoryDatabase: + services.AddMemoryCache(); + services.AddScoped(); + break; - default: - throw new NotSupportedException("Please set the ActiveDatabase in appsettings.json file."); - } + case ActiveDatabase.LocalDb: + case ActiveDatabase.SqlServer: + services.AddDistributedSqlServerCache(options => + { + var cacheOptions = cookieOptions.DistributedSqlServerCacheOptions; + options.ConnectionString = string.IsNullOrWhiteSpace(cacheOptions.ConnectionString) + ? siteSettings.GetMsSqlDbConnectionString() + : cacheOptions.ConnectionString; + options.SchemaName = cacheOptions.SchemaName; + options.TableName = cacheOptions.TableName; + }); + services.AddScoped(); + break; - return services; + default: + throw new NotSupportedException("Please set the ActiveDatabase in appsettings.json file."); } + + return services; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.IocConfig/DbContextOptionsExtensions.cs b/src/ASPNETCoreIdentitySample.IocConfig/DbContextOptionsExtensions.cs index 9560021..92183fe 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/DbContextOptionsExtensions.cs +++ b/src/ASPNETCoreIdentitySample.IocConfig/DbContextOptionsExtensions.cs @@ -1,4 +1,4 @@ -using System; +using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.DataLayer.InMemoryDatabase; using ASPNETCoreIdentitySample.DataLayer.MSSQL; using ASPNETCoreIdentitySample.DataLayer.SQLite; @@ -7,45 +7,56 @@ using DNTCommon.Web.Core; using Microsoft.Extensions.DependencyInjection; -namespace ASPNETCoreIdentitySample.IocConfig +namespace ASPNETCoreIdentitySample.IocConfig; + +public static class DbContextOptionsExtensions { - public static class DbContextOptionsExtensions + public static IServiceCollection AddConfiguredDbContext( + this IServiceCollection serviceCollection, SiteSettings siteSettings) { - public static IServiceCollection AddConfiguredDbContext( - this IServiceCollection serviceCollection, SiteSettings siteSettings) + if (siteSettings == null) { - switch (siteSettings.ActiveDatabase) - { - case ActiveDatabase.InMemoryDatabase: - serviceCollection.AddConfiguredInMemoryDbContext(siteSettings); - break; - - case ActiveDatabase.LocalDb: - case ActiveDatabase.SqlServer: - serviceCollection.AddConfiguredMsSqlDbContext(siteSettings); - break; - - case ActiveDatabase.SQLite: - serviceCollection.AddConfiguredSQLiteDbContext(siteSettings); - break; - - default: - throw new NotSupportedException("Please set the ActiveDatabase in appsettings.json file."); - } - - return serviceCollection; + throw new ArgumentNullException(nameof(siteSettings)); } - /// - /// Creates and seeds the database. - /// - public static void InitializeDb(this IServiceProvider serviceProvider) + serviceCollection.AddInterceptors(); + + switch (siteSettings.ActiveDatabase) { - serviceProvider.RunScopedService(identityDbInitialize => - { - identityDbInitialize.Initialize(); - identityDbInitialize.SeedData(); - }); + case ActiveDatabase.InMemoryDatabase: + serviceCollection.AddConfiguredInMemoryDbContext(siteSettings); + break; + + case ActiveDatabase.LocalDb: + case ActiveDatabase.SqlServer: + serviceCollection.AddConfiguredMsSqlDbContext(siteSettings); + break; + + case ActiveDatabase.SQLite: + serviceCollection.AddConfiguredSQLiteDbContext(siteSettings); + break; + + default: + throw new NotSupportedException("Please set the ActiveDatabase in appsettings.json file."); } + + return serviceCollection; + } + + /// + /// Creates and seeds the database. + /// + public static void InitializeDb(this IServiceProvider serviceProvider) + { + serviceProvider.RunScopedService(identityDbInitialize => + { + identityDbInitialize.Initialize(); + identityDbInitialize.SeedData(); + }); + } + + private static void AddInterceptors(this IServiceCollection services) + { + services.AddSingleton(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.IocConfig/IdentityServicesRegistry.cs b/src/ASPNETCoreIdentitySample.IocConfig/IdentityServicesRegistry.cs index afc667c..be44768 100644 --- a/src/ASPNETCoreIdentitySample.IocConfig/IdentityServicesRegistry.cs +++ b/src/ASPNETCoreIdentitySample.IocConfig/IdentityServicesRegistry.cs @@ -1,33 +1,27 @@ -using System; -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; +using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -namespace ASPNETCoreIdentitySample.IocConfig +namespace ASPNETCoreIdentitySample.IocConfig; + +public static class IdentityServicesRegistry { - public static class IdentityServicesRegistry + /// + /// Adds all of the ASP.NET Core Identity related services and configurations at once. + /// + public static void AddCustomIdentityServices(this IServiceCollection services, IConfiguration configuration) { - /// - /// Adds all of the ASP.NET Core Identity related services and configurations at once. - /// - public static void AddCustomIdentityServices(this IServiceCollection services) - { - var siteSettings = GetSiteSettings(services); - services.AddIdentityOptions(siteSettings); - services.AddConfiguredDbContext(siteSettings); - services.AddCustomServices(); - services.AddCustomTicketStore(siteSettings); - services.AddDynamicPermissions(); - services.AddCustomDataProtection(siteSettings); - } + var siteSettings = GetSiteSettings(configuration); + services.AddIdentityOptions(siteSettings); + services.AddConfiguredDbContext(siteSettings); + services.AddCustomServices(); + services.AddCustomTicketStore(siteSettings); + services.AddDynamicPermissions(); + services.AddCustomDataProtection(siteSettings); + } - public static SiteSettings GetSiteSettings(this IServiceCollection services) - { - var provider = services.BuildServiceProvider(); - var siteSettingsOptions = provider.GetRequiredService>(); - var siteSettings = siteSettingsOptions.Value; - if (siteSettings == null) throw new ArgumentNullException(nameof(siteSettings)); - return siteSettings; - } + public static SiteSettings GetSiteSettings(this IConfiguration configuration) + { + return configuration.Get(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.MsTests/ASPNETCoreIdentitySample.MsTests.csproj b/src/ASPNETCoreIdentitySample.MsTests/ASPNETCoreIdentitySample.MsTests.csproj index e81f739..38b0000 100644 --- a/src/ASPNETCoreIdentitySample.MsTests/ASPNETCoreIdentitySample.MsTests.csproj +++ b/src/ASPNETCoreIdentitySample.MsTests/ASPNETCoreIdentitySample.MsTests.csproj @@ -1,7 +1,6 @@  - net5.0 - RCS1090 + net6.0 @@ -14,10 +13,10 @@ - - - - + + + + diff --git a/src/ASPNETCoreIdentitySample.MsTests/CoreTests.cs b/src/ASPNETCoreIdentitySample.MsTests/CoreTests.cs index ad2622f..9b577d4 100644 --- a/src/ASPNETCoreIdentitySample.MsTests/CoreTests.cs +++ b/src/ASPNETCoreIdentitySample.MsTests/CoreTests.cs @@ -1,62 +1,59 @@ using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.IocConfig; +using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Linq; -using System; -using ASPNETCoreIdentitySample.IocConfig; -using DNTCommon.Web.Core; -using ASPNETCoreIdentitySample.Services.Contracts.Identity; using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ASPNETCoreIdentitySample.MsTests; -namespace ASPNETCoreIdentitySample.MsTests +/// +/// More info: http://www.dntips.ir/post/2510 +/// +[TestClass] +public class CoreTests { - /// - /// More info: http://www.dotnettips.info/post/2510 - /// - [TestClass] - public class CoreTests + private readonly IServiceProvider _serviceProvider; + + public CoreTests() { - private readonly IServiceProvider _serviceProvider; - - public CoreTests() - { - var services = new ServiceCollection(); - services.AddOptions(); - services.AddLogging(cfg => cfg.AddConsole().AddDebug()); - services.AddScoped(); - - var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .Build(); - services.Configure(options => configuration.Bind(options)) - .PostConfigure(x => { x.ActiveDatabase = ActiveDatabase.InMemoryDatabase; }); - services.AddSingleton(provider => configuration); - - services.AddCustomIdentityServices(); - services.AddDNTCommonWeb(); - services.AddCloudscribePagination(); - - var siteSettings = services.GetSiteSettings(); - services.AddConfiguredDbContext(siteSettings); - _serviceProvider = services.BuildServiceProvider(); - - var identityDbInitialize = _serviceProvider.GetRequiredService(); - identityDbInitialize.Initialize(); - identityDbInitialize.SeedData(); - } - - [TestMethod] - public void Test_UserAdmin_Exists() + var services = new ServiceCollection(); + services.AddOptions(); + services.AddLogging(cfg => cfg.AddConsole().AddDebug()); + services.AddScoped(); + + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", false, true) + .Build(); + services.Configure(options => configuration.Bind(options)) + .PostConfigure(x => { x.ActiveDatabase = ActiveDatabase.InMemoryDatabase; }); + services.AddSingleton(provider => configuration); + + services.AddCustomIdentityServices(configuration); + services.AddDNTCommonWeb(); + services.AddCloudscribePagination(); + + var siteSettings = configuration.GetSiteSettings(); + services.AddConfiguredDbContext(siteSettings); + _serviceProvider = services.BuildServiceProvider(); + + var identityDbInitialize = _serviceProvider.GetRequiredService(); + identityDbInitialize.Initialize(); + identityDbInitialize.SeedData(); + } + + [TestMethod] + public void TestUserAdminExists() + { + _serviceProvider.RunScopedService(context => { - _serviceProvider.RunScopedService(context => - { - var users = context.Set(); - Assert.IsTrue(users.Any(x => x.UserName == "Admin")); - }); - } + var users = context.Set(); + Assert.IsTrue(users.Any(x => x.UserName == "Admin")); + }); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.MsTests/CustomNormalizerTests.cs b/src/ASPNETCoreIdentitySample.MsTests/CustomNormalizerTests.cs index f1855b4..56a3f5f 100644 --- a/src/ASPNETCoreIdentitySample.MsTests/CustomNormalizerTests.cs +++ b/src/ASPNETCoreIdentitySample.MsTests/CustomNormalizerTests.cs @@ -1,32 +1,23 @@ using ASPNETCoreIdentitySample.Services.Identity; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace ASPNETCoreIdentitySample.MsTests +namespace ASPNETCoreIdentitySample.MsTests; + +/// +/// More info: http://www.dntips.ir/post/2162 +/// +[TestClass] +public class CustomNormalizerTests { - /// - /// More info: http://www.dotnettips.info/post/2162 - /// - [TestClass] - public class CustomNormalizerTests + [TestMethod, DataRow("part1.part2@gmail.com", "PART1PART2@GMAIL.COM"), + DataRow("part1...part2@gmail.com", "PART1PART2@GMAIL.COM"), + DataRow("pa.rt1.par.t2@gmail.com", "PART1PART2@GMAIL.COM"), + DataRow("p.ar.t1.pa.rt.2@gmail.com", "PART1PART2@GMAIL.COM"), + DataRow("part1.part2+spamsite@gmail.com", "PART1PART2@GMAIL.COM"), + DataRow("pa.rt1.par.t2+spam.site@gmail.com", "PART1PART2@GMAIL.COM")] + public void TestGmailAddressWithDotsCanBeNormalized(string actual, string expected) { - [TestMethod] - [DataRow("part1.part2@gmail.com", "PART1PART2@GMAIL.COM")] - [DataRow("part1...part2@gmail.com", "PART1PART2@GMAIL.COM")] - [DataRow("pa.rt1.par.t2@gmail.com", "PART1PART2@GMAIL.COM")] - [DataRow("p.ar.t1.pa.rt.2@gmail.com", "PART1PART2@GMAIL.COM")] - public void Test_Gmail_Address_With_Dots_CanBe_Normalized(string actual, string expected) - { - var customNormalizer = new CustomNormalizer(); - Assert.AreEqual(expected, customNormalizer.NormalizeEmail(actual)); - } - - [TestMethod] - [DataRow("part1.part2+spamsite@gmail.com", "PART1PART2@GMAIL.COM")] - [DataRow("pa.rt1.par.t2+spam.site@gmail.com", "PART1PART2@GMAIL.COM")] - public void Test_Gmail_Address_With_Plus_CanBe_Normalized(string actual, string expected) - { - var customNormalizer = new CustomNormalizer(); - Assert.AreEqual(expected, customNormalizer.NormalizeEmail(actual)); - } + var customNormalizer = new CustomNormalizer(); + Assert.AreEqual(expected, customNormalizer.NormalizeEmail(actual)); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.MsTests/TestHostingEnvironment.cs b/src/ASPNETCoreIdentitySample.MsTests/TestHostingEnvironment.cs index f08fa2f..9627e6e 100644 --- a/src/ASPNETCoreIdentitySample.MsTests/TestHostingEnvironment.cs +++ b/src/ASPNETCoreIdentitySample.MsTests/TestHostingEnvironment.cs @@ -1,25 +1,23 @@ using Microsoft.AspNetCore.Hosting; -using System; using Microsoft.Extensions.FileProviders; -namespace ASPNETCoreIdentitySample.MsTests +namespace ASPNETCoreIdentitySample.MsTests; + +public class TestHostingEnvironment : IWebHostEnvironment { - public class TestHostingEnvironment : IWebHostEnvironment + public TestHostingEnvironment() { - 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; } + 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; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/ASPNETCoreIdentitySample.Services.csproj b/src/ASPNETCoreIdentitySample.Services/ASPNETCoreIdentitySample.Services.csproj index 65ed270..a97a0e1 100644 --- a/src/ASPNETCoreIdentitySample.Services/ASPNETCoreIdentitySample.Services.csproj +++ b/src/ASPNETCoreIdentitySample.Services/ASPNETCoreIdentitySample.Services.csproj @@ -1,7 +1,6 @@  - net5.0 - RCS1090 + net6.0 @@ -10,9 +9,9 @@ - - - - + + + + diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/ICategoryService.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/ICategoryService.cs index 04e3a15..ba225ac 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/ICategoryService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/ICategoryService.cs @@ -1,11 +1,9 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities; +using ASPNETCoreIdentitySample.Entities; -namespace ASPNETCoreIdentitySample.Services.Contracts +namespace ASPNETCoreIdentitySample.Services.Contracts; + +public interface ICategoryService { - public interface ICategoryService - { - void AddNewCategory(Category category); - IList GetAllCategories(); - } + void AddNewCategory(Category category); + IList GetAllCategories(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/IProductService.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/IProductService.cs index 722cc87..536a7e9 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/IProductService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/IProductService.cs @@ -1,11 +1,9 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities; +using ASPNETCoreIdentitySample.Entities; -namespace ASPNETCoreIdentitySample.Services.Contracts +namespace ASPNETCoreIdentitySample.Services.Contracts; + +public interface IProductService { - public interface IProductService - { - void AddNewProduct(Product product); - IList GetAllProducts(); - } + void AddNewProduct(Product product); + IList GetAllProducts(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IAppLogItemsService.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IAppLogItemsService.cs index f84b119..9c54d29 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IAppLogItemsService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IAppLogItemsService.cs @@ -1,15 +1,12 @@ -using System; -using System.Threading.Tasks; -using ASPNETCoreIdentitySample.ViewModels.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface IAppLogItemsService { - public interface IAppLogItemsService - { - Task DeleteAllAsync(string logLevel = ""); - Task DeleteAsync(int logItemId); - Task DeleteOlderThanAsync(DateTime cutoffDateUtc, string logLevel = ""); - Task GetCountAsync(string logLevel = ""); - Task GetPagedAppLogItemsAsync(int pageNumber, int pageSize, SortOrder sortOrder, string logLevel = ""); - } + Task DeleteAllAsync(string logLevel = ""); + Task DeleteAsync(int logItemId); + Task DeleteOlderThanAsync(DateTime cutoffDateUtc, string logLevel = ""); + Task GetCountAsync(string logLevel = ""); + Task GetPagedAppLogItemsAsync(int pageNumber, int pageSize, SortOrder sortOrder, string logLevel = ""); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationRoleManager.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationRoleManager.cs index 66bd917..0a2177a 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationRoleManager.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationRoleManager.cs @@ -1,247 +1,242 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; +using System.Security.Claims; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface IApplicationRoleManager : IDisposable { - public interface IApplicationRoleManager : IDisposable - { - #region BaseClass - - /// - /// Gets an IQueryable collection of Roles if the persistence store is an , - /// otherwise throws a . - /// - /// An IQueryable collection of Roles if the persistence store is an . - /// Thrown if the persistence store is not an . - /// - /// Callers to this property should use to ensure the backing role store supports - /// returning an IQueryable list of roles. - /// - IQueryable Roles { get; } - - /// - /// Gets the normalizer to use when normalizing role names to keys. - /// - /// - /// The normalizer to use when normalizing role names to keys. - /// - ILookupNormalizer KeyNormalizer { get; set; } - - /// - /// Gets the used to provider error messages. - /// - /// - /// The used to provider error messages. - /// - IdentityErrorDescriber ErrorDescriber { get; set; } - - /// - /// Gets a list of validators for roles to call before persistence. - /// - /// A list of validators for roles to call before persistence. - IList> RoleValidators { get; } - - /// - /// Gets the used to log messages from the manager. - /// - /// - /// The used to log messages from the manager. - /// - ILogger Logger { get; set; } - - /// - /// Gets a flag indicating whether the underlying persistence store supports returning an collection of roles. - /// - /// - /// true if the underlying persistence store supports returning an collection of roles, otherwise false. - /// - bool SupportsQueryableRoles { get; } - - /// - /// Gets a flag indicating whether the underlying persistence store supports s for roles. - /// - /// - /// true if the underlying persistence store supports s for roles, otherwise false. - /// - bool SupportsRoleClaims { get; } - - /// - /// Adds a claim to a role. - /// - /// The role to add the claim to. - /// The claim to add. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task AddClaimAsync(Role role, Claim claim); - - /// - /// Creates the specified in the persistence store. - /// - /// The role to create. - /// - /// The that represents the asynchronous operation. - /// - Task CreateAsync(Role role); - - /// - /// Deletes the specified . - /// - /// The role to delete. - /// - /// The that represents the asynchronous operation, containing the for the delete. - /// - Task DeleteAsync(Role role); - - /// - /// Finds the role associated with the specified if any. - /// - /// The role ID whose role should be returned. - /// - /// The that represents the asynchronous operation, containing the role - /// associated with the specified - /// - Task FindByIdAsync(string roleId); - - /// - /// Finds the role associated with the specified if any. - /// - /// The name of the role to be returned. - /// - /// The that represents the asynchronous operation, containing the role - /// associated with the specified - /// - Task FindByNameAsync(string roleName); - - /// - /// Gets a list of claims associated with the specified . - /// - /// The role whose claims should be returned. - /// - /// The that represents the asynchronous operation, containing the list of s - /// associated with the specified . - /// - Task> GetClaimsAsync(Role role); - - /// - /// Gets a normalized representation of the specified . - /// - /// The value to normalize. - /// A normalized representation of the specified . - string NormalizeKey(string key); - - /// - /// Removes a claim from a role. - /// - /// The role to remove the claim from. - /// The claim to remove. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task RemoveClaimAsync(Role role, Claim claim); - - /// - /// Gets a flag indicating whether the specified exists. - /// - /// The role name whose existence should be checked. - /// - /// The that represents the asynchronous operation, containing true if the role name exists, otherwise false. - /// - Task RoleExistsAsync(string roleName); - - /// - /// Updates the specified . - /// - /// The role to updated. - /// - /// The that represents the asynchronous operation, containing the for the update. - /// - Task UpdateAsync(Role role); - - /// - /// Updates the normalized name for the specified . - /// - /// The role whose normalized name needs to be updated. - /// - /// The that represents the asynchronous operation. - /// - Task UpdateNormalizedRoleNameAsync(Role role); - - /// - /// Gets the name of the specified . - /// - /// The role whose name should be retrieved. - /// - /// The that represents the asynchronous operation, containing the name of the - /// specified . - /// - Task GetRoleNameAsync(Role role); - - /// - /// Sets the name of the specified . - /// - /// The role whose name should be set. - /// The name to set. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task SetRoleNameAsync(Role role, string name); - - /// - /// Gets the ID of the specified . - /// - /// The role whose ID should be retrieved. - /// - /// The that represents the asynchronous operation, containing the ID of the - /// specified . - /// - Task GetRoleIdAsync(Role role); - - #endregion - - #region CustomMethods - - IList FindUserRoles(int userId); - - Task> GetAllCustomRolesAsync(); - - IList GetApplicationUsersInRole(string roleName); - - IList GetRolesForCurrentUser(); - - IList GetRolesForUser(int userId); - - IList GetUserRolesInRole(string roleName); - - bool IsCurrentUserInRole(string roleName); - - bool IsUserInRole(int userId, string roleName); - - IList GetAllCustomRolesAndUsersCountList(); - - Task GetPagedApplicationUsersInRoleListAsync( - int roleId, - int pageNumber, int recordsPerPage, - string sortByField, SortOrder sortOrder, - bool showAllUsers); - - Task FindRoleIncludeRoleClaimsAsync(int roleId); - - Task AddOrUpdateRoleClaimsAsync( - int roleId, - string roleClaimType, - IList selectedRoleClaimValues); - - #endregion - } + #region BaseClass + + /// + /// Gets an IQueryable collection of Roles if the persistence store is an , + /// otherwise throws a . + /// + /// An IQueryable collection of Roles if the persistence store is an . + /// Thrown if the persistence store is not an . + /// + /// Callers to this property should use to ensure the backing role store supports + /// returning an IQueryable list of roles. + /// + IQueryable Roles { get; } + + /// + /// Gets the normalizer to use when normalizing role names to keys. + /// + /// + /// The normalizer to use when normalizing role names to keys. + /// + ILookupNormalizer KeyNormalizer { get; set; } + + /// + /// Gets the used to provider error messages. + /// + /// + /// The used to provider error messages. + /// + IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// Gets a list of validators for roles to call before persistence. + /// + /// A list of validators for roles to call before persistence. + IList> RoleValidators { get; } + + /// + /// Gets the used to log messages from the manager. + /// + /// + /// The used to log messages from the manager. + /// + ILogger Logger { get; set; } + + /// + /// Gets a flag indicating whether the underlying persistence store supports returning an collection of roles. + /// + /// + /// true if the underlying persistence store supports returning an collection of roles, otherwise false. + /// + bool SupportsQueryableRoles { get; } + + /// + /// Gets a flag indicating whether the underlying persistence store supports s for roles. + /// + /// + /// true if the underlying persistence store supports s for roles, otherwise false. + /// + bool SupportsRoleClaims { get; } + + /// + /// Adds a claim to a role. + /// + /// The role to add the claim to. + /// The claim to add. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task AddClaimAsync(Role role, Claim claim); + + /// + /// Creates the specified in the persistence store. + /// + /// The role to create. + /// + /// The that represents the asynchronous operation. + /// + Task CreateAsync(Role role); + + /// + /// Deletes the specified . + /// + /// The role to delete. + /// + /// The that represents the asynchronous operation, containing the for the delete. + /// + Task DeleteAsync(Role role); + + /// + /// Finds the role associated with the specified if any. + /// + /// The role ID whose role should be returned. + /// + /// The that represents the asynchronous operation, containing the role + /// associated with the specified + /// + Task FindByIdAsync(string roleId); + + /// + /// Finds the role associated with the specified if any. + /// + /// The name of the role to be returned. + /// + /// The that represents the asynchronous operation, containing the role + /// associated with the specified + /// + Task FindByNameAsync(string roleName); + + /// + /// Gets a list of claims associated with the specified . + /// + /// The role whose claims should be returned. + /// + /// The that represents the asynchronous operation, containing the list of s + /// associated with the specified . + /// + Task> GetClaimsAsync(Role role); + + /// + /// Gets a normalized representation of the specified . + /// + /// The value to normalize. + /// A normalized representation of the specified . + string NormalizeKey(string key); + + /// + /// Removes a claim from a role. + /// + /// The role to remove the claim from. + /// The claim to remove. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task RemoveClaimAsync(Role role, Claim claim); + + /// + /// Gets a flag indicating whether the specified exists. + /// + /// The role name whose existence should be checked. + /// + /// The that represents the asynchronous operation, containing true if the role name exists, otherwise false. + /// + Task RoleExistsAsync(string roleName); + + /// + /// Updates the specified . + /// + /// The role to updated. + /// + /// The that represents the asynchronous operation, containing the for the update. + /// + Task UpdateAsync(Role role); + + /// + /// Updates the normalized name for the specified . + /// + /// The role whose normalized name needs to be updated. + /// + /// The that represents the asynchronous operation. + /// + Task UpdateNormalizedRoleNameAsync(Role role); + + /// + /// Gets the name of the specified . + /// + /// The role whose name should be retrieved. + /// + /// The that represents the asynchronous operation, containing the name of the + /// specified . + /// + Task GetRoleNameAsync(Role role); + + /// + /// Sets the name of the specified . + /// + /// The role whose name should be set. + /// The name to set. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task SetRoleNameAsync(Role role, string name); + + /// + /// Gets the ID of the specified . + /// + /// The role whose ID should be retrieved. + /// + /// The that represents the asynchronous operation, containing the ID of the + /// specified . + /// + Task GetRoleIdAsync(Role role); + + #endregion + + #region CustomMethods + + IList FindUserRoles(int userId); + + Task> GetAllCustomRolesAsync(); + + IList GetApplicationUsersInRole(string roleName); + + IList GetRolesForCurrentUser(); + + IList GetRolesForUser(int userId); + + IList GetUserRolesInRole(string roleName); + + bool IsCurrentUserInRole(string roleName); + + bool IsUserInRole(int userId, string roleName); + + IList GetAllCustomRolesAndUsersCountList(); + + Task GetPagedApplicationUsersInRoleListAsync( + int roleId, + int pageNumber, int recordsPerPage, + string sortByField, SortOrder sortOrder, + bool showAllUsers); + + Task FindRoleIncludeRoleClaimsAsync(int roleId); + + Task AddOrUpdateRoleClaimsAsync( + int roleId, + string roleClaimType, + IList selectedRoleClaimValues); + + #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationRoleStore.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationRoleStore.cs index b519566..86adad5 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationRoleStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationRoleStore.cs @@ -1,162 +1,197 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; +using System.Security.Claims; using ASPNETCoreIdentitySample.Entities.Identity; using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface IApplicationRoleStore : IDisposable { - public interface IApplicationRoleStore : IDisposable - { - #region BaseClass - - /// - /// Gets or sets the for any error that occurred with the current operation. - /// - IdentityErrorDescriber ErrorDescriber { get; set; } - - /// - /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. - /// - /// - /// True if changes should be automatically persisted, otherwise false. - /// - bool AutoSaveChanges { get; set; } - - /// - /// A navigation property for the roles the store contains. - /// - IQueryable Roles { get; } - - /// - /// Adds the given to the specified . - /// - /// The role to add the claim to. - /// The claim to add to the role. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - /// Task AddClaimAsync(Role role, Claim claim, CancellationToken cancellationToken = default); - - /// - /// Converts the provided to a strongly typed key object. - /// - /// The id to convert. - /// An instance of representing the provided . - int ConvertIdFromString(string id); - - /// - /// Converts the provided to its string representation. - /// - /// The id to convert. - /// An representation of the provided . - string ConvertIdToString(int id); - - /// - /// Creates a new role in a store as an asynchronous operation. - /// - /// The role to create in the store. - /// The used to propagate notifications that the operation should be canceled. - /// A that represents the of the asynchronous query. - Task CreateAsync(Role role, CancellationToken cancellationToken = default); - - /// - /// Deletes a role from the store as an asynchronous operation. - /// - /// The role to delete from the store. - /// The used to propagate notifications that the operation should be canceled. - /// A that represents the of the asynchronous query. - Task DeleteAsync(Role role, CancellationToken cancellationToken = default); - - /// - /// Gets the ID for a role from the store as an asynchronous operation. - /// - /// The role whose ID should be returned. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the ID of the role. - Task GetRoleIdAsync(Role role, CancellationToken cancellationToken = default); - - /// - /// Gets the name of a role from the store as an asynchronous operation. - /// - /// /// The role whose name should be returned. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the name of the role. - Task GetRoleNameAsync(Role role, CancellationToken cancellationToken = default); - - /// - /// Sets the name of a role in the store as an asynchronous operation. - /// - /// The role whose name should be set. - /// The name of the role. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task SetRoleNameAsync(Role role, string roleName, CancellationToken cancellationToken = default); - - /// - /// Finds the role who has the specified ID as an asynchronous operation. - /// - /// The role ID to look for. - /// The used to propagate notifications that the operation should be canceled. - /// A that result of the look up. - Task FindByIdAsync(string id, CancellationToken cancellationToken = default); - - /// - /// Finds the role who has the specified normalized name as an asynchronous operation. - /// - /// The normalized role name to look for. - /// The used to propagate notifications that the operation should be canceled. - /// A that result of the look up. - Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default); - - /// - /// Get the claims associated with the specified as an asynchronous operation. - /// - /// The role whose claims should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the claims granted to a role. - Task> GetClaimsAsync(Role role, CancellationToken cancellationToken = default); - - /// - /// Get a role's normalized name as an asynchronous operation. - /// - /// The role whose normalized name should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the name of the role. - Task GetNormalizedRoleNameAsync(Role role, CancellationToken cancellationToken = default); - - /// - /// Removes the given from the specified . - /// - /// The role to remove the claim from. - /// The claim to remove from the role. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task RemoveClaimAsync(Role role, Claim claim, CancellationToken cancellationToken = default); - - /// - /// Set a role's normalized name as an asynchronous operation. - /// - /// The role whose normalized name should be set. - /// The normalized name to set - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task SetNormalizedRoleNameAsync(Role role, string normalizedName, CancellationToken cancellationToken = default); - - /// - /// Updates a role in a store as an asynchronous operation. - /// - /// The role to update in the store. - /// The used to propagate notifications that the operation should be canceled. - /// A that represents the of the asynchronous query. - Task UpdateAsync(Role role, CancellationToken cancellationToken = default); - - #endregion - - #region CustomMethods - - #endregion - } + #region BaseClass + + /// + /// Gets or sets the for any error that occurred with the current operation. + /// + IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are + /// called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + bool AutoSaveChanges { get; set; } + + /// + /// A navigation property for the roles the store contains. + /// + IQueryable Roles { get; } + + /// + /// Adds the given to the specified . + /// + /// The role to add the claim to. + /// The claim to add to the role. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// The that represents the asynchronous operation. + Task AddClaimAsync(Role role, Claim claim, CancellationToken cancellationToken = default); + + /// + /// Converts the provided to a strongly typed key object. + /// + /// The id to convert. + /// An instance of TKey representing the provided . + int ConvertIdFromString(string id); + + /// + /// Converts the provided to its string representation. + /// + /// The id to convert. + /// An representation of the provided . + string ConvertIdToString(int id); + + /// + /// Creates a new role in a store as an asynchronous operation. + /// + /// The role to create in the store. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// A that represents the of the asynchronous query. + Task CreateAsync(Role role, CancellationToken cancellationToken = default); + + /// + /// Deletes a role from the store as an asynchronous operation. + /// + /// The role to delete from the store. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// A that represents the of the asynchronous query. + Task DeleteAsync(Role role, CancellationToken cancellationToken = default); + + /// + /// Gets the ID for a role from the store as an asynchronous operation. + /// + /// The role whose ID should be returned. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// A that contains the ID of the role. + Task GetRoleIdAsync(Role role, CancellationToken cancellationToken = default); + + /// + /// Gets the name of a role from the store as an asynchronous operation. + /// + /// /// + /// The role whose name should be returned. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// A that contains the name of the role. + Task GetRoleNameAsync(Role role, CancellationToken cancellationToken = default); + + /// + /// Sets the name of a role in the store as an asynchronous operation. + /// + /// The role whose name should be set. + /// The name of the role. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// The that represents the asynchronous operation. + Task SetRoleNameAsync(Role role, string roleName, CancellationToken cancellationToken = default); + + /// + /// Finds the role who has the specified ID as an asynchronous operation. + /// + /// The role ID to look for. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// A that result of the look up. + Task FindByIdAsync(string id, CancellationToken cancellationToken = default); + + /// + /// Finds the role who has the specified normalized name as an asynchronous operation. + /// + /// The normalized role name to look for. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// A that result of the look up. + Task FindByNameAsync(string normalizedName, CancellationToken cancellationToken = default); + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The role whose claims should be retrieved. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// A that contains the claims granted to a role. + Task> GetClaimsAsync(Role role, CancellationToken cancellationToken = default); + + /// + /// Get a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be retrieved. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// A that contains the name of the role. + Task GetNormalizedRoleNameAsync(Role role, CancellationToken cancellationToken = default); + + /// + /// Removes the given from the specified . + /// + /// The role to remove the claim from. + /// The claim to remove from the role. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// The that represents the asynchronous operation. + Task RemoveClaimAsync(Role role, Claim claim, CancellationToken cancellationToken = default); + + /// + /// Set a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be set. + /// The normalized name to set + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// The that represents the asynchronous operation. + Task SetNormalizedRoleNameAsync(Role role, string normalizedName, CancellationToken cancellationToken = default); + + /// + /// Updates a role in a store as an asynchronous operation. + /// + /// The role to update in the store. + /// + /// The used to propagate notifications that the operation + /// should be canceled. + /// + /// A that represents the of the asynchronous query. + Task UpdateAsync(Role role, CancellationToken cancellationToken = default); + + #endregion + + #region CustomMethods + + #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationSignInManager.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationSignInManager.cs index bc100fc..bdef70b 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationSignInManager.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationSignInManager.cs @@ -1,311 +1,351 @@ -using System.Collections.Generic; -using System.Security.Claims; -using System.Threading.Tasks; +using System.Security.Claims; using ASPNETCoreIdentitySample.Entities.Identity; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface IApplicationSignInManager { - public interface IApplicationSignInManager - { - #region BaseClass - - /// - /// Gets the used to log messages from the manager. - /// - /// - /// The used to log messages from the manager. - /// - ILogger Logger { get; set; } - - /// - /// The used. - /// - UserManager UserManager { get; set; } - - /// - /// The used. - /// - IUserClaimsPrincipalFactory ClaimsFactory { get; set; } - - /// - /// The used. - /// - IdentityOptions Options { get; set; } - - /// - /// Creates a for the specified , as an asynchronous operation. - /// - /// The user to create a for. - /// The task object representing the asynchronous operation, containing the ClaimsPrincipal for the specified user. - Task CreateUserPrincipalAsync(User user); - - /// - /// Returns true if the principal has an identity with the application cookie identity - /// - /// The instance. - /// True if the user is logged in with identity. - bool IsSignedIn(ClaimsPrincipal principal); - - /// - /// Returns a flag indicating whether the specified user can sign in. - /// - /// The user whose sign-in status should be returned. - /// - /// The task object representing the asynchronous operation, containing a flag that is true - /// if the specified user can sign-in, otherwise false. - /// - Task CanSignInAsync(User user); - - /// - /// Regenerates the user's application cookie, whilst preserving the existing - /// AuthenticationProperties like rememberMe, as an asynchronous operation. - /// - /// The user whose sign-in cookie should be refreshed. - /// The task object representing the asynchronous operation. - Task RefreshSignInAsync(User user); - - /// - /// Signs in the specified . - /// - /// The user to sign-in. - /// Flag indicating whether the sign-in cookie should persist after the browser is closed. - /// Name of the method used to authenticate the user. - /// The task object representing the asynchronous operation. - Task SignInAsync(User user, bool isPersistent, string authenticationMethod = null); - - /// - /// Signs in the specified . - /// - /// The user to sign-in. - /// Properties applied to the login and authentication cookie. - /// Name of the method used to authenticate the user. - /// The task object representing the asynchronous operation. - Task SignInAsync(User user, Microsoft.AspNetCore.Authentication.AuthenticationProperties authenticationProperties, string authenticationMethod = null); - - /// - /// Signs the current user out of the application. - /// - Task SignOutAsync(); - - /// - /// Validates the security stamp for the specified against - /// the persisted stamp for the current user, as an asynchronous operation. - /// - /// The principal whose stamp should be validated. - /// The task object representing the asynchronous operation. The task will contain the - /// if the stamp matches the persisted value, otherwise it will return false. - Task ValidateSecurityStampAsync(ClaimsPrincipal principal); - - /// - /// Attempts to sign in the specified and combination - /// as an asynchronous operation. - /// - /// The user to sign in. - /// The password to attempt to sign in with. - /// Flag indicating whether the sign-in cookie should persist after the browser is closed. - /// Flag indicating if the user account should be locked if the sign in fails. - /// The task object representing the asynchronous operation containing the - /// for the sign-in attempt. - Task PasswordSignInAsync(User user, string password, - bool isPersistent, bool lockoutOnFailure); - - /// - /// Attempts to sign in the specified and combination - /// as an asynchronous operation. - /// - /// The user name to sign in. - /// The password to attempt to sign in with. - /// Flag indicating whether the sign-in cookie should persist after the browser is closed. - /// Flag indicating if the user account should be locked if the sign in fails. - /// The task object representing the asynchronous operation containing the - /// for the sign-in attempt. - Task PasswordSignInAsync(string userName, string password, - bool isPersistent, bool lockoutOnFailure); - - /// - /// Attempts a password sign in for a user. - /// - /// The user to sign in. - /// The password to attempt to sign in with. - /// Flag indicating if the user account should be locked if the sign in fails. - /// The task object representing the asynchronous operation containing the - /// for the sign-in attempt. - /// - Task CheckPasswordSignInAsync(User user, string password, bool lockoutOnFailure); - - /// - /// Returns a flag indicating if the current client browser has been remembered by two factor authentication - /// for the user attempting to login, as an asynchronous operation. - /// - /// The user attempting to login. - /// - /// The task object representing the asynchronous operation containing true if the browser has been remembered - /// for the current user. - /// - Task IsTwoFactorClientRememberedAsync(User user); - - /// - /// Sets a flag on the browser to indicate the user has selected "Remember this browser" for two factor authentication purposes, - /// as an asynchronous operation. - /// - /// The user who choose "remember this browser". - /// The task object representing the asynchronous operation. - Task RememberTwoFactorClientAsync(User user); - - /// - /// Clears the "Remember this browser flag" from the current browser, as an asynchronous operation. - /// - /// The task object representing the asynchronous operation. - Task ForgetTwoFactorClientAsync(); - - /// - /// Signs in the user without two factor authentication using a two factor recovery code. - /// - /// The two factor recovery code. - /// - Task TwoFactorRecoveryCodeSignInAsync(string recoveryCode); - - /// - /// Validates the sign in code from an authenticator app and creates and signs in the user, as an asynchronous operation. - /// - /// The two factor authentication code to validate. - /// Flag indicating whether the sign-in cookie should persist after the browser is closed. - /// Flag indicating whether the current browser should be remember, suppressing all further - /// two factor authentication prompts. - /// The task object representing the asynchronous operation containing the - /// for the sign-in attempt. - Task TwoFactorAuthenticatorSignInAsync(string code, bool isPersistent, bool rememberClient); - - /// - /// Validates the two faction sign in code and creates and signs in the user, as an asynchronous operation. - /// - /// The two factor authentication provider to validate the code against. - /// The two factor authentication code to validate. - /// Flag indicating whether the sign-in cookie should persist after the browser is closed. - /// Flag indicating whether the current browser should be remember, suppressing all further - /// two factor authentication prompts. - /// The task object representing the asynchronous operation containing the - /// for the sign-in attempt. - Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient); - - /// - /// Gets the for the current two factor authentication login, as an asynchronous operation. - /// - /// The task object representing the asynchronous operation containing the - /// for the sign-in attempt. - Task GetTwoFactorAuthenticationUserAsync(); - - /// - /// Signs in a user via a previously registered third party login, as an asynchronous operation. - /// - /// The login provider to use. - /// The unique provider identifier for the user. - /// Flag indicating whether the sign-in cookie should persist after the browser is closed. - /// The task object representing the asynchronous operation containing the - /// for the sign-in attempt. - Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent); - - /// - /// Signs in a user via a previously registered third party login, as an asynchronous operation. - /// - /// The login provider to use. - /// The unique provider identifier for the user. - /// Flag indicating whether the sign-in cookie should persist after the browser is closed. - /// Flag indicating whether to bypass two factor authentication. - /// The task object representing the asynchronous operation containing the - /// for the sign-in attempt. - Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, bool bypassTwoFactor); - - /// - /// Gets a collection of s for the known external login providers. - /// - /// A collection of s for the known external login providers. - Task> GetExternalAuthenticationSchemesAsync(); - - /// - /// Gets the external login information for the current login, as an asynchronous operation. - /// - /// Flag indication whether a Cross Site Request Forgery token was expected in the current request. - /// The task object representing the asynchronous operation containing the - /// for the sign-in attempt. - Task GetExternalLoginInfoAsync(string expectedXsrf = null); - - /// - /// Stores any authentication tokens found in the external authentication cookie into the associated user. - /// - /// The information from the external login provider. - /// The that represents the asynchronous operation, containing the of the operation. - Task UpdateExternalAuthenticationTokensAsync(ExternalLoginInfo externalLogin); - - /// - /// Configures the redirect URL and user identifier for the specified external login . - /// - /// The provider to configure. - /// The external login URL users should be redirected to during the login flow. - /// The current user's identifier, which will be used to provide CSRF protection. - /// A configured . - Microsoft.AspNetCore.Authentication.AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string userId = null); - - /// - /// Signs in the specified if is set to false. - /// Otherwise stores the for use after a two factor check. - /// - /// - /// Flag indicating whether the sign-in cookie should persist after the browser is closed. - /// The login provider to use. Default is null - /// Flag indicating whether to bypass two factor authentication. Default is false - /// Returns a - Task SignInOrTwoFactorAsync(User user, bool isPersistent, string loginProvider = null, bool bypassTwoFactor = false); - - /// - /// Used to determine if a user is considered locked out. - /// - /// The user. - /// Whether a user is considered locked out. - Task IsLockedOut(User user); - - /// - /// Returns a locked out SignInResult. - /// - /// The user. - /// A locked out SignInResult - Task LockedOut(User user); - - /// - /// Used to ensure that a user is allowed to sign in. - /// - /// The user - /// Null if the user should be allowed to sign in, otherwise the SignInResult why they should be denied. - Task PreSignInCheck(User user); - - /// - /// Used to reset a user's lockout count. - /// - /// The user - /// The that represents the asynchronous operation, containing the of the operation. - Task ResetLockout(User user); - - #endregion - - #region CustomMethods - - /// - /// Returns true if the current user has an identity with the application cookie identity - /// - /// True if the current user is logged in with identity. - bool IsCurrentUserSignedIn(); - - /// - /// Validates the security stamp for the current user against - /// the persisted stamp for the current user, as an asynchronous operation. - /// - /// The task object representing the asynchronous operation. The task will contain the current user - /// if the stamp matches the persisted value, otherwise it will return false. - Task ValidateCurrentUserSecurityStampAsync(); - - #endregion - } + #region BaseClass + + /// + /// Gets the used to log messages from the manager. + /// + /// + /// The used to log messages from the manager. + /// + ILogger Logger { get; set; } + + /// + /// The used. + /// + UserManager UserManager { get; set; } + + /// + /// The used. + /// + IUserClaimsPrincipalFactory ClaimsFactory { get; set; } + + /// + /// The used. + /// + IdentityOptions Options { get; set; } + + /// + /// Creates a for the specified , as an asynchronous operation. + /// + /// The user to create a for. + /// + /// The task object representing the asynchronous operation, containing the ClaimsPrincipal for the specified + /// user. + /// + Task CreateUserPrincipalAsync(User user); + + /// + /// Returns true if the principal has an identity with the application cookie identity + /// + /// The instance. + /// True if the user is logged in with identity. + bool IsSignedIn(ClaimsPrincipal principal); + + /// + /// Returns a flag indicating whether the specified user can sign in. + /// + /// The user whose sign-in status should be returned. + /// + /// The task object representing the asynchronous operation, containing a flag that is true + /// if the specified user can sign-in, otherwise false. + /// + Task CanSignInAsync(User user); + + /// + /// Regenerates the user's application cookie, whilst preserving the existing + /// AuthenticationProperties like rememberMe, as an asynchronous operation. + /// + /// The user whose sign-in cookie should be refreshed. + /// The task object representing the asynchronous operation. + Task RefreshSignInAsync(User user); + + /// + /// Signs in the specified . + /// + /// The user to sign-in. + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// Name of the method used to authenticate the user. + /// The task object representing the asynchronous operation. + Task SignInAsync(User user, bool isPersistent, string authenticationMethod = null); + + /// + /// Signs in the specified . + /// + /// The user to sign-in. + /// Properties applied to the login and authentication cookie. + /// Name of the method used to authenticate the user. + /// The task object representing the asynchronous operation. + Task SignInAsync(User user, AuthenticationProperties authenticationProperties, string authenticationMethod = null); + + /// + /// Signs the current user out of the application. + /// + Task SignOutAsync(); + + /// + /// Validates the security stamp for the specified against + /// the persisted stamp for the current user, as an asynchronous operation. + /// + /// The principal whose stamp should be validated. + /// + /// The task object representing the asynchronous operation. The task will contain the User + /// if the stamp matches the persisted value, otherwise it will return false. + /// + Task ValidateSecurityStampAsync(ClaimsPrincipal principal); + + /// + /// Attempts to sign in the specified and combination + /// as an asynchronous operation. + /// + /// The user to sign in. + /// The password to attempt to sign in with. + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// Flag indicating if the user account should be locked if the sign in fails. + /// + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + /// + Task PasswordSignInAsync(User user, string password, + bool isPersistent, bool lockoutOnFailure); + + /// + /// Attempts to sign in the specified and combination + /// as an asynchronous operation. + /// + /// The user name to sign in. + /// The password to attempt to sign in with. + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// Flag indicating if the user account should be locked if the sign in fails. + /// + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + /// + Task PasswordSignInAsync(string userName, string password, + bool isPersistent, bool lockoutOnFailure); + + /// + /// Attempts a password sign in for a user. + /// + /// The user to sign in. + /// The password to attempt to sign in with. + /// Flag indicating if the user account should be locked if the sign in fails. + /// + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + /// + /// + Task CheckPasswordSignInAsync(User user, string password, bool lockoutOnFailure); + + /// + /// Returns a flag indicating if the current client browser has been remembered by two factor authentication + /// for the user attempting to login, as an asynchronous operation. + /// + /// The user attempting to login. + /// + /// The task object representing the asynchronous operation containing true if the browser has been remembered + /// for the current user. + /// + Task IsTwoFactorClientRememberedAsync(User user); + + /// + /// Sets a flag on the browser to indicate the user has selected "Remember this browser" for two factor authentication + /// purposes, + /// as an asynchronous operation. + /// + /// The user who choose "remember this browser". + /// The task object representing the asynchronous operation. + Task RememberTwoFactorClientAsync(User user); + + /// + /// Clears the "Remember this browser flag" from the current browser, as an asynchronous operation. + /// + /// The task object representing the asynchronous operation. + Task ForgetTwoFactorClientAsync(); + + /// + /// Signs in the user without two factor authentication using a two factor recovery code. + /// + /// The two factor recovery code. + /// + Task TwoFactorRecoveryCodeSignInAsync(string recoveryCode); + + /// + /// Validates the sign in code from an authenticator app and creates and signs in the user, as an asynchronous + /// operation. + /// + /// The two factor authentication code to validate. + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// + /// Flag indicating whether the current browser should be remember, suppressing all further + /// two factor authentication prompts. + /// + /// + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + /// + Task TwoFactorAuthenticatorSignInAsync(string code, bool isPersistent, bool rememberClient); + + /// + /// Validates the two faction sign in code and creates and signs in the user, as an asynchronous operation. + /// + /// The two factor authentication provider to validate the code against. + /// The two factor authentication code to validate. + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// + /// Flag indicating whether the current browser should be remember, suppressing all further + /// two factor authentication prompts. + /// + /// + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + /// + Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient); + + /// + /// Gets the User for the current two factor authentication login, as an asynchronous operation. + /// + /// + /// The task object representing the asynchronous operation containing the User/> + /// for the sign-in attempt. + /// + Task GetTwoFactorAuthenticationUserAsync(); + + /// + /// Signs in a user via a previously registered third party login, as an asynchronous operation. + /// + /// The login provider to use. + /// The unique provider identifier for the user. + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + /// + Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent); + + /// + /// Signs in a user via a previously registered third party login, as an asynchronous operation. + /// + /// The login provider to use. + /// The unique provider identifier for the user. + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// Flag indicating whether to bypass two factor authentication. + /// + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + /// + Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, + bool bypassTwoFactor); + + /// + /// Gets a collection of s for the known external login providers. + /// + /// A collection of s for the known external login providers. + Task> GetExternalAuthenticationSchemesAsync(); + + /// + /// Gets the external login information for the current login, as an asynchronous operation. + /// + /// + /// Flag indication whether a Cross Site Request Forgery token was expected in the current + /// request. + /// + /// + /// The task object representing the asynchronous operation containing the + /// for the sign-in attempt. + /// + Task GetExternalLoginInfoAsync(string expectedXsrf = null); + + /// + /// Stores any authentication tokens found in the external authentication cookie into the associated user. + /// + /// The information from the external login provider. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task UpdateExternalAuthenticationTokensAsync(ExternalLoginInfo externalLogin); + + /// + /// Configures the redirect URL and user identifier for the specified external login . + /// + /// The provider to configure. + /// The external login URL users should be redirected to during the login flow. + /// The current user's identifier, which will be used to provide CSRF protection. + /// A configured . + AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, + string userId = null); + + /// + /// Signs in the specified if is set to false. + /// Otherwise stores the for use after a two factor check. + /// + /// + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// The login provider to use. Default is null + /// Flag indicating whether to bypass two factor authentication. Default is false + /// Returns a + Task SignInOrTwoFactorAsync(User user, bool isPersistent, string loginProvider = null, + bool bypassTwoFactor = false); + + /// + /// Used to determine if a user is considered locked out. + /// + /// The user. + /// Whether a user is considered locked out. + Task IsLockedOut(User user); + + /// + /// Returns a locked out SignInResult. + /// + /// The user. + /// A locked out SignInResult + Task LockedOut(User user); + + /// + /// Used to ensure that a user is allowed to sign in. + /// + /// The user + /// Null if the user should be allowed to sign in, otherwise the SignInResult why they should be denied. + Task PreSignInCheck(User user); + + /// + /// Used to reset a user's lockout count. + /// + /// The user + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ResetLockout(User user); + + #endregion + + #region CustomMethods + + /// + /// Returns true if the current user has an identity with the application cookie identity + /// + /// True if the current user is logged in with identity. + bool IsCurrentUserSignedIn(); + + /// + /// Validates the security stamp for the current user against + /// the persisted stamp for the current user, as an asynchronous operation. + /// + /// + /// The task object representing the asynchronous operation. The task will contain the current user + /// if the stamp matches the persisted value, otherwise it will return false. + /// + Task ValidateCurrentUserSecurityStampAsync(); + + #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationUserManager.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationUserManager.cs index debbb5a..d2d93a8 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationUserManager.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationUserManager.cs @@ -1,1070 +1,1129 @@ -using ASPNETCoreIdentitySample.Entities.Identity; +using System.Security.Claims; +using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; using Microsoft.AspNetCore.Identity; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using System; using Microsoft.Extensions.Logging; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface IApplicationUserManager : IDisposable { - public interface IApplicationUserManager : IDisposable - { - #region BaseClass - - /// - /// The used to log messages from the manager. - /// - /// - /// The used to log messages from the manager. - /// - ILogger Logger { get; set; } - - /// - /// The used to hash passwords. - /// - IPasswordHasher PasswordHasher { get; set; } - - /// - /// The used to validate users. - /// - IList> UserValidators { get; } - - /// - /// The used to validate passwords. - /// - IList> PasswordValidators { get; } - - /// - /// The used to normalize things like user and role names. - /// - ILookupNormalizer KeyNormalizer { get; set; } - - /// - /// The used to generate error messages. - /// - IdentityErrorDescriber ErrorDescriber { get; set; } - - /// - /// The used to configure Identity. - /// - IdentityOptions Options { get; set; } - - /// - /// Gets a flag indicating whether the backing user store supports authentication tokens. - /// - /// - /// true if the backing user store supports authentication tokens, otherwise false. - /// - bool SupportsUserAuthenticationTokens { get; } - - /// - /// Gets a flag indicating whether the backing user store supports a user authenticator. - /// - /// - /// true if the backing user store supports a user authenticatior, otherwise false. - /// - bool SupportsUserAuthenticatorKey { get; } - - /// - /// Gets a flag indicating whether the backing user store supports recovery codes. - /// - /// - /// true if the backing user store supports a user authenticatior, otherwise false. - /// - bool SupportsUserTwoFactorRecoveryCodes { get; } - - /// - /// Gets a flag indicating whether the backing user store supports two factor authentication. - /// - /// - /// true if the backing user store supports user two factor authentication, otherwise false. - /// - bool SupportsUserTwoFactor { get; } - - /// - /// Gets a flag indicating whether the backing user store supports user passwords. - /// - /// - /// true if the backing user store supports user passwords, otherwise false. - /// - bool SupportsUserPassword { get; } - - /// - /// Gets a flag indicating whether the backing user store supports security stamps. - /// - /// - /// true if the backing user store supports user security stamps, otherwise false. - /// - bool SupportsUserSecurityStamp { get; } - - /// - /// Gets a flag indicating whether the backing user store supports user roles. - /// - /// - /// true if the backing user store supports user roles, otherwise false. - /// - bool SupportsUserRole { get; } - - /// - /// Gets a flag indicating whether the backing user store supports external logins. - /// - /// - /// true if the backing user store supports external logins, otherwise false. - /// - bool SupportsUserLogin { get; } - - /// - /// Gets a flag indicating whether the backing user store supports user emails. - /// - /// - /// true if the backing user store supports user emails, otherwise false. - /// - bool SupportsUserEmail { get; } - - /// - /// Gets a flag indicating whether the backing user store supports user telephone numbers. - /// - /// - /// true if the backing user store supports user telephone numbers, otherwise false. - /// - bool SupportsUserPhoneNumber { get; } - - /// - /// Gets a flag indicating whether the backing user store supports user claims. - /// - /// - /// true if the backing user store supports user claims, otherwise false. - /// - bool SupportsUserClaim { get; } - - /// - /// Gets a flag indicating whether the backing user store supports user lock-outs. - /// - /// - /// true if the backing user store supports user lock-outs, otherwise false. - /// - bool SupportsUserLockout { get; } - - /// - /// Gets a flag indicating whether the backing user store supports returning - /// collections of information. - /// - /// - /// true if the backing user store supports returning collections of - /// information, otherwise false. - /// - bool SupportsQueryableUsers { get; } - - /// - /// Returns an IQueryable of users if the store is an IQueryableUserStore - /// - IQueryable Users { get; } - - /// - /// Returns the Name claim value if present otherwise returns null. - /// - /// The instance. - /// The Name claim value, or null if the claim is not present. - /// The Name claim is identified by . - string GetUserName(ClaimsPrincipal principal); - - /// - /// Returns the User ID claim value if present otherwise returns null. - /// - /// The instance. - /// The User ID claim value, or null if the claim is not present. - /// The User ID claim is identified by . - string GetUserId(ClaimsPrincipal principal); - - /// - /// Returns the user corresponding to the IdentityOptions.ClaimsIdentity.UserIdClaimType claim in - /// the principal or null. - /// - /// The principal which contains the user id claim. - /// The user corresponding to the IdentityOptions.ClaimsIdentity.UserIdClaimType claim in - /// the principal or null - Task GetUserAsync(ClaimsPrincipal principal); - - /// - /// Generates a value suitable for use in concurrency tracking. - /// - /// The user to generate the stamp for. - /// - /// The that represents the asynchronous operation, containing the security - /// stamp for the specified . - /// - Task GenerateConcurrencyStampAsync(User user); - - /// - /// Creates the specified in the backing store with no password, - /// as an asynchronous operation. - /// - /// The user to create. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task CreateAsync(User user); - - /// - /// Updates the specified in the backing store. - /// - /// The user to update. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task UpdateAsync(User user); - - /// - /// Deletes the specified from the backing store. - /// - /// The user to delete. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task DeleteAsync(User user); - - /// - /// Finds and returns a user, if any, who has the specified . - /// - /// The user ID to search for. - /// - /// The that represents the asynchronous operation, containing the user matching the specified if it exists. - /// - Task FindByIdAsync(string userId); - - /// - /// Finds and returns a user, if any, who has the specified user name. - /// - /// The user name to search for. - /// - /// The that represents the asynchronous operation, containing the user matching the specified if it exists. - /// - Task FindByNameAsync(string userName); - - /// - /// Creates the specified in the backing store with given password, - /// as an asynchronous operation. - /// - /// The user to create. - /// The password for the user to hash and store. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task CreateAsync(User user, string password); - - /// - /// Normalize a key (user name, email) for consistent comparisons. - /// - /// The key to normalize. - /// A normalized value representing the specified . - string NormalizeEmail(string email); - - /// - /// Normalize a key (user name, email) for consistent comparisons. - /// - /// The key to normalize. - /// A normalized value representing the specified . - string NormalizeName(string name); - - /// - /// Updates the normalized user name for the specified . - /// - /// The user whose user name should be normalized and updated. - /// The that represents the asynchronous operation. - Task UpdateNormalizedUserNameAsync(User user); - - /// - /// Gets the user name for the specified . - /// - /// The user whose name should be retrieved. - /// The that represents the asynchronous operation, containing the name for the specified . - Task GetUserNameAsync(User user); - - /// - /// Sets the given for the specified . - /// - /// The user whose name should be set. - /// The user name to set. - /// The that represents the asynchronous operation. - Task SetUserNameAsync(User user, string userName); - - /// - /// Gets the user identifier for the specified . - /// - /// The user whose identifier should be retrieved. - /// The that represents the asynchronous operation, containing the identifier for the specified . - Task GetUserIdAsync(User user); - - /// - /// Returns a flag indicating whether the given is valid for the - /// specified . - /// - /// The user whose password should be validated. - /// The password to validate - /// The that represents the asynchronous operation, containing true if - /// the specified matches the one store for the , - /// otherwise false. - Task CheckPasswordAsync(User user, string password); - - /// - /// Gets a flag indicating whether the specified has a password. - /// - /// The user to return a flag for, indicating whether they have a password or not. - /// - /// The that represents the asynchronous operation, returning true if the specified has a password - /// otherwise false. - /// - Task HasPasswordAsync(User user); - - /// - /// Adds the to the specified only if the user - /// does not already have a password. - /// - /// The user whose password should be set. - /// The password to set. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task AddPasswordAsync(User user, string password); - - /// - /// Changes a user's password after confirming the specified is correct, - /// as an asynchronous operation. - /// - /// The user whose password should be set. - /// The current password to validate before changing. - /// The new password to set for the specified . - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task ChangePasswordAsync(User user, string currentPassword, string newPassword); - - /// - /// Removes a user's password. - /// - /// The user whose password should be removed. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task RemovePasswordAsync(User user); - - /// - /// Returns a indicating the result of a password hash comparison. - /// - /// The store containing a user's password. - /// The user whose password should be verified. - /// The password to verify. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task VerifyPasswordAsync(IUserPasswordStore store, User user, string password); - - /// - /// Get the security stamp for the specified . - /// - /// The user whose security stamp should be set. - /// The that represents the asynchronous operation, containing the security stamp for the specified . - Task GetSecurityStampAsync(User user); - - /// - /// Regenerates the security stamp for the specified . - /// - /// The user whose security stamp should be regenerated. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - /// - /// Regenerating a security stamp will sign out any saved login for the user. - /// - Task UpdateSecurityStampAsync(User user); - - /// - /// Generates a password reset token for the specified , using - /// the configured password reset token provider. - /// - /// The user to generate a password reset token for. - /// The that represents the asynchronous operation, - /// containing a password reset token for the specified . - Task GeneratePasswordResetTokenAsync(User user); - - /// - /// Resets the 's password to the specified after - /// validating the given password reset . - /// - /// The user whose password should be reset. - /// The password reset token to verify. - /// The new password to set if reset token verification fails. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task ResetPasswordAsync(User user, string token, string newPassword); - - /// - /// Retrieves the user associated with the specified external login provider and login provider key. - /// - /// The login provider who provided the . - /// The key provided by the to identify a user. - /// - /// The for the asynchronous operation, containing the user, if any which matched the specified login provider and key. - /// - Task FindByLoginAsync(string loginProvider, string providerKey); - - /// - /// Attempts to remove the provided external login information from the specified . - /// and returns a flag indicating whether the removal succeed or not. - /// - /// The user to remove the login information from. - /// The login provide whose information should be removed. - /// The key given by the external login provider for the specified user. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task RemoveLoginAsync(User user, string loginProvider, string providerKey); - - /// - /// Adds an external to the specified . - /// - /// The user to add the login to. - /// The external to add to the specified . - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task AddLoginAsync(User user, UserLoginInfo login); - - /// - /// Retrieves the associated logins for the specified . - /// - /// The user whose associated logins to retrieve. - /// - /// The for the asynchronous operation, containing a list of for the specified , if any. - /// - Task> GetLoginsAsync(User user); - - /// - /// Adds the specified to the . - /// - /// The user to add the claim to. - /// The claim to add. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task AddClaimAsync(User user, Claim claim); - - /// - /// Adds the specified to the . - /// - /// The user to add the claim to. - /// The claims to add. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task AddClaimsAsync(User user, IEnumerable claims); - - /// - /// Replaces the given on the specified with the - /// - /// The user to replace the claim on. - /// The claim to replace. - /// The new claim to replace the existing with. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task ReplaceClaimAsync(User user, Claim claim, Claim newClaim); - - /// - /// Removes the specified from the given . - /// - /// The user to remove the specified from. - /// The to remove. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task RemoveClaimAsync(User user, Claim claim); - - /// - /// Removes the specified from the given . - /// - /// The user to remove the specified from. - /// A collection of s to remove. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task RemoveClaimsAsync(User user, IEnumerable claims); - - /// - /// Gets a list of s to be belonging to the specified as an asynchronous operation. - /// - /// The user whose claims to retrieve. - /// - /// A that represents the result of the asynchronous query, a list of s. - /// - Task> GetClaimsAsync(User user); - - /// - /// Add the specified to the named role. - /// - /// The user to add to the named role. - /// The name of the role to add the user to. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task AddToRoleAsync(User user, string role); - - /// - /// Add the specified to the named roles. - /// - /// The user to add to the named roles. - /// The name of the roles to add the user to. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task AddToRolesAsync(User user, IEnumerable roles); - - /// - /// Removes the specified from the named role. - /// - /// The user to remove from the named role. - /// The name of the role to remove the user from. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task RemoveFromRoleAsync(User user, string role); - - /// - /// Removes the specified from the named roles. - /// - /// The user to remove from the named roles. - /// The name of the roles to remove the user from. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task RemoveFromRolesAsync(User user, IEnumerable roles); - - /// - /// Gets a list of role names the specified belongs to. - /// - /// The user whose role names to retrieve. - /// The that represents the asynchronous operation, containing a list of role names. - Task> GetRolesAsync(User user); - - /// - /// Returns a flag indicating whether the specified is a member of the give named role. - /// - /// The user whose role membership should be checked. - /// The name of the role to be checked. - /// - /// The that represents the asynchronous operation, containing a flag indicating whether the specified is - /// a member of the named role. - /// - Task IsInRoleAsync(User user, string role); - - /// - /// Gets the email address for the specified . - /// - /// The user whose email should be returned. - /// The task object containing the results of the asynchronous operation, the email address for the specified . - Task GetEmailAsync(User user); - - /// - /// Sets the address for a . - /// - /// The user whose email should be set. - /// The email to set. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task SetEmailAsync(User user, string email); - - /// - /// Gets the user, if any, associated with the normalized value of the specified email address. - /// - /// The email address to return the user for. - /// - /// The task object containing the results of the asynchronous lookup operation, the user, if any, associated with a normalized value of the specified email address. - /// - Task FindByEmailAsync(string email); - - /// - /// Updates the normalized email for the specified . - /// - /// The user whose email address should be normalized and updated. - /// The task object representing the asynchronous operation. - Task UpdateNormalizedEmailAsync(User user); - - /// - /// Generates an email confirmation token for the specified user. - /// - /// The user to generate an email confirmation token for. - /// - /// The that represents the asynchronous operation, an email confirmation token. - /// - Task GenerateEmailConfirmationTokenAsync(User user); - - /// - /// Validates that an email confirmation token matches the specified . - /// - /// The user to validate the token against. - /// The email confirmation token to validate. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task ConfirmEmailAsync(User user, string token); - - /// - /// Gets a flag indicating whether the email address for the specified has been verified, true if the email address is verified otherwise - /// false. - /// - /// The user whose email confirmation status should be returned. - /// - /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified - /// has been confirmed or not. - /// - Task IsEmailConfirmedAsync(User user); - - /// - /// Generates an email change token for the specified user. - /// - /// The user to generate an email change token for. - /// The new email address. - /// - /// The that represents the asynchronous operation, an email change token. - /// - Task GenerateChangeEmailTokenAsync(User user, string newEmail); - - /// - /// Updates a users emails if the specified email change is valid for the user. - /// - /// The user whose email should be updated. - /// The new email address. - /// The change email token to be verified. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task ChangeEmailAsync(User user, string newEmail, string token); - - /// - /// Gets the telephone number, if any, for the specified . - /// - /// The user whose telephone number should be retrieved. - /// The that represents the asynchronous operation, containing the user's telephone number, if any. - Task GetPhoneNumberAsync(User user); - - /// - /// Sets the phone number for the specified . - /// - /// The user whose phone number to set. - /// The phone number to set. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task SetPhoneNumberAsync(User user, string phoneNumber); - - /// - /// Sets the phone number for the specified if the specified - /// change is valid. - /// - /// The user whose phone number to set. - /// The phone number to set. - /// The phone number confirmation token to validate. - /// - /// The that represents the asynchronous operation, containing the - /// of the operation. - /// - Task ChangePhoneNumberAsync(User user, string phoneNumber, string token); - - /// - /// Gets a flag indicating whether the specified 's telephone number has been confirmed. - /// - /// The user to return a flag for, indicating whether their telephone number is confirmed. - /// - /// The that represents the asynchronous operation, returning true if the specified has a confirmed - /// telephone number otherwise false. - /// - Task IsPhoneNumberConfirmedAsync(User user); - - /// - /// Generates a telephone number change token for the specified user. - /// - /// The user to generate a telephone number token for. - /// The new phone number the validation token should be sent to. - /// - /// The that represents the asynchronous operation, containing the telephone change number token. - /// - Task GenerateChangePhoneNumberTokenAsync(User user, string phoneNumber); - - /// - /// Returns a flag indicating whether the specified 's phone number change verification - /// token is valid for the given . - /// - /// The user to validate the token against. - /// The telephone number change token to validate. - /// The telephone number the token was generated for. - /// - /// The that represents the asynchronous operation, returning true if the - /// is valid, otherwise false. - /// - Task VerifyChangePhoneNumberTokenAsync(User user, string token, string phoneNumber); - - /// - /// Returns a flag indicating whether the specified is valid for - /// the given and . - /// - /// The user to validate the token against. - /// The token provider used to generate the token. - /// The purpose the token should be generated for. - /// The token to validate - /// - /// The that represents the asynchronous operation, returning true if the - /// is valid, otherwise false. - /// - Task VerifyUserTokenAsync(User user, string tokenProvider, string purpose, string token); - - /// - /// Generates a token for the given and . - /// - /// The user the token will be for. - /// The provider which will generate the token. - /// The purpose the token will be for. - /// - /// The that represents result of the asynchronous operation, a token for - /// the given user and purpose. - /// - Task GenerateUserTokenAsync(User user, string tokenProvider, string purpose); - - /// - /// Registers a token provider. - /// - /// The name of the provider to register. - /// The provider to register. - void RegisterTokenProvider(string providerName, IUserTwoFactorTokenProvider provider); - - /// - /// Gets a list of valid two factor token providers for the specified , - /// as an asynchronous operation. - /// - /// The user the whose two factor authentication providers will be returned. - /// - /// The that represents result of the asynchronous operation, a list of two - /// factor authentication providers for the specified user. - /// - Task> GetValidTwoFactorProvidersAsync(User user); - - /// - /// Verifies the specified two factor authentication against the . - /// - /// The user the token is supposed to be for. - /// The provider which will verify the token. - /// The token to verify. - /// - /// The that represents result of the asynchronous operation, true if the token is valid, - /// otherwise false. - /// - Task VerifyTwoFactorTokenAsync(User user, string tokenProvider, string token); - - /// - /// Gets a two factor authentication token for the specified . - /// - /// The user the token is for. - /// The provider which will generate the token. - /// - /// The that represents result of the asynchronous operation, a two factor authentication token - /// for the user. - /// - Task GenerateTwoFactorTokenAsync(User user, string tokenProvider); - - /// - /// Returns a flag indicating whether the specified has two factor authentication enabled or not, - /// as an asynchronous operation. - /// - /// The user whose two factor authentication enabled status should be retrieved. - /// - /// The that represents the asynchronous operation, true if the specified - /// has two factor authentication enabled, otherwise false. - /// - Task GetTwoFactorEnabledAsync(User user); - - /// - /// Sets a flag indicating whether the specified has two factor authentication enabled or not, - /// as an asynchronous operation. - /// - /// The user whose two factor authentication enabled status should be set. - /// A flag indicating whether the specified has two factor authentication enabled. - /// - /// The that represents the asynchronous operation, the of the operation - /// - Task SetTwoFactorEnabledAsync(User user, bool enabled); - - /// - /// Returns a flag indicating whether the specified his locked out, - /// as an asynchronous operation. - /// - /// The user whose locked out status should be retrieved. - /// - /// The that represents the asynchronous operation, true if the specified - /// is locked out, otherwise false. - /// - Task IsLockedOutAsync(User user); - - /// - /// Sets a flag indicating whether the specified is locked out, - /// as an asynchronous operation. - /// - /// The user whose locked out status should be set. - /// Flag indicating whether the user is locked out or not. - /// - /// The that represents the asynchronous operation, the of the operation - /// - Task SetLockoutEnabledAsync(User user, bool enabled); - - /// - /// Retrieves a flag indicating whether user lockout can enabled for the specified user. - /// - /// The user whose ability to be locked out should be returned. - /// - /// The that represents the asynchronous operation, true if a user can be locked out, otherwise false. - /// - Task GetLockoutEnabledAsync(User user); - - /// - /// Gets the last a user's last lockout expired, if any. - /// Any time in the past should be indicates a user is not locked out. - /// - /// The user whose lockout date should be retrieved. - /// - /// A that represents the lookup, a containing the last time a user's lockout expired, if any. - /// - Task GetLockoutEndDateAsync(User user); - - /// - /// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a user. - /// - /// The user whose lockout date should be set. - /// The after which the 's lockout should end. - /// The that represents the asynchronous operation, containing the of the operation. - Task SetLockoutEndDateAsync(User user, DateTimeOffset? lockoutEnd); - - /// - /// Increments the access failed count for the user as an asynchronous operation. - /// If the failed access account is greater than or equal to the configured maximum number of attempts, - /// the user will be locked out for the configured lockout time span. - /// - /// The user whose failed access count to increment. - /// The that represents the asynchronous operation, containing the of the operation. - Task AccessFailedAsync(User user); - - /// - /// Resets the access failed count for the specified . - /// - /// The user whose failed access count should be reset. - /// The that represents the asynchronous operation, containing the of the operation. - Task ResetAccessFailedCountAsync(User user); - - /// - /// Retrieves the current number of failed accesses for the given . - /// - /// The user whose access failed count should be retrieved for. - /// The that contains the result the asynchronous operation, the current failed access count - /// for the user. - Task GetAccessFailedCountAsync(User user); - - /// - /// Returns a list of users from the user store who have the specified . - /// - /// The claim to look for. - /// - /// A that represents the result of the asynchronous query, a list of s who - /// have the specified claim. - /// - Task> GetUsersForClaimAsync(Claim claim); - - /// - /// Returns a list of users from the user store who are members of the specified . - /// - /// The name of the role whose users should be returned. - /// - /// A that represents the result of the asynchronous query, a list of s who - /// are members of the specified role. - /// - Task> GetUsersInRoleAsync(string roleName); - - /// - /// Returns an authentication token for a user. - /// - /// - /// The authentication scheme for the provider the token is associated with. - /// The name of the token. - /// The authentication token for a user - Task GetAuthenticationTokenAsync(User user, string loginProvider, string tokenName); - - /// - /// Sets an authentication token for a user. - /// - /// - /// The authentication scheme for the provider the token is associated with. - /// The name of the token. - /// The value of the token. - /// Whether the user was successfully updated. - Task SetAuthenticationTokenAsync(User user, string loginProvider, string tokenName, string tokenValue); - - /// - /// Remove an authentication token for a user. - /// - /// - /// The authentication scheme for the provider the token is associated with. - /// The name of the token. - /// Whether a token was removed. - Task RemoveAuthenticationTokenAsync(User user, string loginProvider, string tokenName); - - /// - /// Returns the authenticator key for the user. - /// - /// The user. - /// The authenticator key - Task GetAuthenticatorKeyAsync(User user); - - /// - /// Resets the authenticator key for the user. - /// - /// The user. - /// Whether the user was successfully updated. - Task ResetAuthenticatorKeyAsync(User user); - - /// - /// Generates a new base32 encoded 160-bit security secret (size of SHA1 hash). - /// - /// The new security secret. - string GenerateNewAuthenticatorKey(); - - /// - /// Generates recovery codes for the user, this invalidates any previous recovery codes for the user. - /// - /// The user to generate recovery codes for. - /// The number of codes to generate. - /// The new recovery codes for the user. Note: there may be less than number returned, as duplicates will be removed. - Task> GenerateNewTwoFactorRecoveryCodesAsync(User user, int number); - - /// - /// Generate a new recovery code. - /// - /// - string CreateTwoFactorRecoveryCode(); - - /// - /// Returns whether a recovery code is valid for a user. Note: recovery codes are only valid - /// once, and will be invalid after use. - /// - /// The user who owns the recovery code. - /// The recovery code to use. - /// True if the recovery code was found for the user. - Task RedeemTwoFactorRecoveryCodeAsync(User user, string code); - - /// - /// Returns how many recovery code are still valid for a user. - /// - /// The user. - /// How many recovery code are still valid for a user. - Task CountRecoveryCodesAsync(User user); - - /// - /// Creates bytes to use as a security token from the user's security stamp. - /// - /// The user. - /// The security token bytes. - Task CreateSecurityTokenAsync(User user); - - /// - /// Updates a user's password hash. - /// - /// The user. - /// The new password. - /// Whether to validate the password. - /// Whether the password has was successfully updated. - Task UpdatePasswordHash(User user, string newPassword, bool validatePassword); - - #endregion - - #region CustomMethods - - User FindById(int userId); - - Task> GetAllUsersAsync(); - - /// - /// Returns the current user corresponding or null. - /// - /// Returns the current user corresponding or null. - User GetCurrentUser(); - - /// - /// Returns the current user corresponding or null. - /// - /// Returns the current user corresponding or null. - Task GetCurrentUserAsync(); - - /// - /// Returns the current User ID claim value if present otherwise returns null. - /// - /// The current User ID claim value, or null if the claim is not present. - string GetCurrentUserId(); - - /// - /// Returns the current User ID claim value if present otherwise returns null. - /// - int? CurrentUserId { get; } - - /// - /// Returns the current user Name claim value if present otherwise returns null. - /// - /// The current user Name claim value, or null if the claim is not present. - string GetCurrentUserName(); - - Task HasPasswordAsync(int userId); - - Task HasPhoneNumberAsync(int userId); - - Task GetEmailImageAsync(int? userId); - - Task GetPagedUsersListAsync( - int pageNumber, int recordsPerPage, - string sortByField, SortOrder sortOrder, - bool showAllUsers); - - Task FindByIdIncludeUserRolesAsync(int userId); - - Task UpdateUserAndSecurityStampAsync(int userId, Action action); - - Task AddOrUpdateUserRolesAsync(int userId, IList selectedRoleIds, Action action = null); - - Task GetPagedUsersListAsync(SearchUsersViewModel model, int pageNumber); - - #endregion - } + #region BaseClass + + /// + /// The used to log messages from the manager. + /// + /// + /// The used to log messages from the manager. + /// + ILogger Logger { get; set; } + + /// + /// The used to hash passwords. + /// + IPasswordHasher PasswordHasher { get; set; } + + /// + /// The used to validate users. + /// + IList> UserValidators { get; } + + /// + /// The used to validate passwords. + /// + IList> PasswordValidators { get; } + + /// + /// The used to normalize things like user and role names. + /// + ILookupNormalizer KeyNormalizer { get; set; } + + /// + /// The used to generate error messages. + /// + IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// The used to configure Identity. + /// + IdentityOptions Options { get; set; } + + /// + /// Gets a flag indicating whether the backing user store supports authentication tokens. + /// + /// + /// true if the backing user store supports authentication tokens, otherwise false. + /// + bool SupportsUserAuthenticationTokens { get; } + + /// + /// Gets a flag indicating whether the backing user store supports a user authenticator. + /// + /// + /// true if the backing user store supports a user authenticatior, otherwise false. + /// + bool SupportsUserAuthenticatorKey { get; } + + /// + /// Gets a flag indicating whether the backing user store supports recovery codes. + /// + /// + /// true if the backing user store supports a user authenticatior, otherwise false. + /// + bool SupportsUserTwoFactorRecoveryCodes { get; } + + /// + /// Gets a flag indicating whether the backing user store supports two factor authentication. + /// + /// + /// true if the backing user store supports user two factor authentication, otherwise false. + /// + bool SupportsUserTwoFactor { get; } + + /// + /// Gets a flag indicating whether the backing user store supports user passwords. + /// + /// + /// true if the backing user store supports user passwords, otherwise false. + /// + bool SupportsUserPassword { get; } + + /// + /// Gets a flag indicating whether the backing user store supports security stamps. + /// + /// + /// true if the backing user store supports user security stamps, otherwise false. + /// + bool SupportsUserSecurityStamp { get; } + + /// + /// Gets a flag indicating whether the backing user store supports user roles. + /// + /// + /// true if the backing user store supports user roles, otherwise false. + /// + bool SupportsUserRole { get; } + + /// + /// Gets a flag indicating whether the backing user store supports external logins. + /// + /// + /// true if the backing user store supports external logins, otherwise false. + /// + bool SupportsUserLogin { get; } + + /// + /// Gets a flag indicating whether the backing user store supports user emails. + /// + /// + /// true if the backing user store supports user emails, otherwise false. + /// + bool SupportsUserEmail { get; } + + /// + /// Gets a flag indicating whether the backing user store supports user telephone numbers. + /// + /// + /// true if the backing user store supports user telephone numbers, otherwise false. + /// + bool SupportsUserPhoneNumber { get; } + + /// + /// Gets a flag indicating whether the backing user store supports user claims. + /// + /// + /// true if the backing user store supports user claims, otherwise false. + /// + bool SupportsUserClaim { get; } + + /// + /// Gets a flag indicating whether the backing user store supports user lock-outs. + /// + /// + /// true if the backing user store supports user lock-outs, otherwise false. + /// + bool SupportsUserLockout { get; } + + /// + /// Gets a flag indicating whether the backing user store supports returning + /// collections of information. + /// + /// + /// true if the backing user store supports returning collections of + /// information, otherwise false. + /// + bool SupportsQueryableUsers { get; } + + /// + /// Returns an IQueryable of users if the store is an IQueryableUserStore + /// + IQueryable Users { get; } + + /// + /// Returns the Name claim value if present otherwise returns null. + /// + /// The instance. + /// The Name claim value, or null if the claim is not present. + /// The Name claim is identified by . + string GetUserName(ClaimsPrincipal principal); + + /// + /// Returns the User ID claim value if present otherwise returns null. + /// + /// The instance. + /// The User ID claim value, or null if the claim is not present. + /// The User ID claim is identified by . + string GetUserId(ClaimsPrincipal principal); + + /// + /// Returns the user corresponding to the IdentityOptions.ClaimsIdentity.UserIdClaimType claim in + /// the principal or null. + /// + /// The principal which contains the user id claim. + /// + /// The user corresponding to the IdentityOptions.ClaimsIdentity.UserIdClaimType claim in + /// the principal or null + /// + Task GetUserAsync(ClaimsPrincipal principal); + + /// + /// Generates a value suitable for use in concurrency tracking. + /// + /// The user to generate the stamp for. + /// + /// The that represents the asynchronous operation, containing the security + /// stamp for the specified . + /// + Task GenerateConcurrencyStampAsync(User user); + + /// + /// Creates the specified in the backing store with no password, + /// as an asynchronous operation. + /// + /// The user to create. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task CreateAsync(User user); + + /// + /// Creates the specified in the backing store with given password, + /// as an asynchronous operation. + /// + /// The user to create. + /// The password for the user to hash and store. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task CreateAsync(User user, string password); + + /// + /// Updates the specified in the backing store. + /// + /// The user to update. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task UpdateAsync(User user); + + /// + /// Deletes the specified from the backing store. + /// + /// The user to delete. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task DeleteAsync(User user); + + /// + /// Finds and returns a user, if any, who has the specified . + /// + /// The user ID to search for. + /// + /// The that represents the asynchronous operation, containing the user matching the specified + /// if it exists. + /// + Task FindByIdAsync(string userId); + + /// + /// Finds and returns a user, if any, who has the specified user name. + /// + /// The user name to search for. + /// + /// The that represents the asynchronous operation, containing the user matching the specified + /// if it exists. + /// + Task FindByNameAsync(string userName); + + /// + /// Normalize a key (user name, email) for consistent comparisons. + /// + /// The key to normalize. + /// A normalized value representing the specified . + string NormalizeEmail(string email); + + /// + /// Normalize a key (user name, email) for consistent comparisons. + /// + /// The key to normalize. + /// A normalized value representing the specified . + string NormalizeName(string name); + + /// + /// Updates the normalized user name for the specified . + /// + /// The user whose user name should be normalized and updated. + /// The that represents the asynchronous operation. + Task UpdateNormalizedUserNameAsync(User user); + + /// + /// Gets the user name for the specified . + /// + /// The user whose name should be retrieved. + /// + /// The that represents the asynchronous operation, containing the name for the specified + /// . + /// + Task GetUserNameAsync(User user); + + /// + /// Sets the given for the specified . + /// + /// The user whose name should be set. + /// The user name to set. + /// The that represents the asynchronous operation. + Task SetUserNameAsync(User user, string userName); + + /// + /// Gets the user identifier for the specified . + /// + /// The user whose identifier should be retrieved. + /// + /// The that represents the asynchronous operation, containing the identifier for the + /// specified . + /// + Task GetUserIdAsync(User user); + + /// + /// Returns a flag indicating whether the given is valid for the + /// specified . + /// + /// The user whose password should be validated. + /// The password to validate + /// + /// The that represents the asynchronous operation, containing true if + /// the specified matches the one store for the , + /// otherwise false. + /// + Task CheckPasswordAsync(User user, string password); + + /// + /// Gets a flag indicating whether the specified has a password. + /// + /// The user to return a flag for, indicating whether they have a password or not. + /// + /// The that represents the asynchronous operation, returning true if the specified + /// has a password + /// otherwise false. + /// + Task HasPasswordAsync(User user); + + Task HasPasswordAsync(int userId); + + /// + /// Adds the to the specified only if the user + /// does not already have a password. + /// + /// The user whose password should be set. + /// The password to set. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task AddPasswordAsync(User user, string password); + + /// + /// Changes a user's password after confirming the specified is correct, + /// as an asynchronous operation. + /// + /// The user whose password should be set. + /// The current password to validate before changing. + /// The new password to set for the specified . + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ChangePasswordAsync(User user, string currentPassword, string newPassword); + + /// + /// Removes a user's password. + /// + /// The user whose password should be removed. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task RemovePasswordAsync(User user); + + /// + /// Returns a indicating the result of a password hash comparison. + /// + /// The store containing a user's password. + /// The user whose password should be verified. + /// The password to verify. + /// + /// The that represents the asynchronous operation, containing the + /// + /// of the operation. + /// + Task VerifyPasswordAsync(IUserPasswordStore store, User user, string password); + + /// + /// Get the security stamp for the specified . + /// + /// The user whose security stamp should be set. + /// + /// The that represents the asynchronous operation, containing the security stamp for the + /// specified . + /// + Task GetSecurityStampAsync(User user); + + /// + /// Regenerates the security stamp for the specified . + /// + /// The user whose security stamp should be regenerated. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + /// + /// Regenerating a security stamp will sign out any saved login for the user. + /// + Task UpdateSecurityStampAsync(User user); + + /// + /// Generates a password reset token for the specified , using + /// the configured password reset token provider. + /// + /// The user to generate a password reset token for. + /// + /// The that represents the asynchronous operation, + /// containing a password reset token for the specified . + /// + Task GeneratePasswordResetTokenAsync(User user); + + /// + /// Resets the 's password to the specified after + /// validating the given password reset . + /// + /// The user whose password should be reset. + /// The password reset token to verify. + /// The new password to set if reset token verification fails. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ResetPasswordAsync(User user, string token, string newPassword); + + /// + /// Retrieves the user associated with the specified external login provider and login provider key. + /// + /// The login provider who provided the . + /// The key provided by the to identify a user. + /// + /// The for the asynchronous operation, containing the user, if any which matched the specified + /// login provider and key. + /// + Task FindByLoginAsync(string loginProvider, string providerKey); + + /// + /// Attempts to remove the provided external login information from the specified . + /// and returns a flag indicating whether the removal succeed or not. + /// + /// The user to remove the login information from. + /// The login provide whose information should be removed. + /// The key given by the external login provider for the specified user. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task RemoveLoginAsync(User user, string loginProvider, string providerKey); + + /// + /// Adds an external to the specified . + /// + /// The user to add the login to. + /// The external to add to the specified . + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task AddLoginAsync(User user, UserLoginInfo login); + + /// + /// Retrieves the associated logins for the specified + /// + /// . + /// + /// The user whose associated logins to retrieve. + /// + /// The for the asynchronous operation, containing a list of for the + /// specified , if any. + /// + Task> GetLoginsAsync(User user); + + /// + /// Adds the specified to the . + /// + /// The user to add the claim to. + /// The claim to add. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task AddClaimAsync(User user, Claim claim); + + /// + /// Adds the specified to the . + /// + /// The user to add the claim to. + /// The claims to add. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task AddClaimsAsync(User user, IEnumerable claims); + + /// + /// Replaces the given on the specified with the + /// + /// + /// The user to replace the claim on. + /// The claim to replace. + /// The new claim to replace the existing with. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ReplaceClaimAsync(User user, Claim claim, Claim newClaim); + + /// + /// Removes the specified from the given . + /// + /// The user to remove the specified from. + /// The to remove. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task RemoveClaimAsync(User user, Claim claim); + + /// + /// Removes the specified from the given . + /// + /// The user to remove the specified from. + /// A collection of s to remove. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task RemoveClaimsAsync(User user, IEnumerable claims); + + /// + /// Gets a list of s to be belonging to the specified as an asynchronous + /// operation. + /// + /// The user whose claims to retrieve. + /// + /// A that represents the result of the asynchronous query, a list of + /// s. + /// + Task> GetClaimsAsync(User user); + + /// + /// Add the specified to the named role. + /// + /// The user to add to the named role. + /// The name of the role to add the user to. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task AddToRoleAsync(User user, string role); + + /// + /// Add the specified to the named roles. + /// + /// The user to add to the named roles. + /// The name of the roles to add the user to. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task AddToRolesAsync(User user, IEnumerable roles); + + /// + /// Removes the specified from the named role. + /// + /// The user to remove from the named role. + /// The name of the role to remove the user from. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task RemoveFromRoleAsync(User user, string role); + + /// + /// Removes the specified from the named roles. + /// + /// The user to remove from the named roles. + /// The name of the roles to remove the user from. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task RemoveFromRolesAsync(User user, IEnumerable roles); + + /// + /// Gets a list of role names the specified belongs to. + /// + /// The user whose role names to retrieve. + /// The that represents the asynchronous operation, containing a list of role names. + Task> GetRolesAsync(User user); + + /// + /// Returns a flag indicating whether the specified is a member of the give named role. + /// + /// The user whose role membership should be checked. + /// The name of the role to be checked. + /// + /// The that represents the asynchronous operation, containing a flag indicating whether the + /// specified is + /// a member of the named role. + /// + Task IsInRoleAsync(User user, string role); + + /// + /// Gets the email address for the specified . + /// + /// The user whose email should be returned. + /// + /// The task object containing the results of the asynchronous operation, the email address for the specified + /// . + /// + Task GetEmailAsync(User user); + + /// + /// Sets the address for a . + /// + /// The user whose email should be set. + /// The email to set. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task SetEmailAsync(User user, string email); + + /// + /// Gets the user, if any, associated with the normalized value of the specified email address. + /// + /// The email address to return the user for. + /// + /// The task object containing the results of the asynchronous lookup operation, the user, if any, associated with a + /// normalized value of the specified email address. + /// + Task FindByEmailAsync(string email); + + /// + /// Updates the normalized email for the specified . + /// + /// The user whose email address should be normalized and updated. + /// The task object representing the asynchronous operation. + Task UpdateNormalizedEmailAsync(User user); + + /// + /// Generates an email confirmation token for the specified user. + /// + /// The user to generate an email confirmation token for. + /// + /// The that represents the asynchronous operation, an email confirmation token. + /// + Task GenerateEmailConfirmationTokenAsync(User user); + + /// + /// Validates that an email confirmation token matches the specified . + /// + /// The user to validate the token against. + /// The email confirmation token to validate. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ConfirmEmailAsync(User user, string token); + + /// + /// Gets a flag indicating whether the email address for the specified has been verified, true + /// if the email address is verified otherwise + /// false. + /// + /// The user whose email confirmation status should be returned. + /// + /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address + /// for the specified + /// has been confirmed or not. + /// + Task IsEmailConfirmedAsync(User user); + + /// + /// Generates an email change token for the specified user. + /// + /// The user to generate an email change token for. + /// The new email address. + /// + /// The that represents the asynchronous operation, an email change token. + /// + Task GenerateChangeEmailTokenAsync(User user, string newEmail); + + /// + /// Updates a users emails if the specified email change is valid for the user. + /// + /// The user whose email should be updated. + /// The new email address. + /// The change email token to be verified. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ChangeEmailAsync(User user, string newEmail, string token); + + /// + /// Gets the telephone number, if any, for the specified . + /// + /// The user whose telephone number should be retrieved. + /// + /// The that represents the asynchronous operation, containing the user's telephone number, if + /// any. + /// + Task GetPhoneNumberAsync(User user); + + /// + /// Sets the phone number for the specified . + /// + /// The user whose phone number to set. + /// The phone number to set. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task SetPhoneNumberAsync(User user, string phoneNumber); + + /// + /// Sets the phone number for the specified if the specified + /// change is valid. + /// + /// The user whose phone number to set. + /// The phone number to set. + /// The phone number confirmation token to validate. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ChangePhoneNumberAsync(User user, string phoneNumber, string token); + + /// + /// Gets a flag indicating whether the specified 's telephone number has been confirmed. + /// + /// The user to return a flag for, indicating whether their telephone number is confirmed. + /// + /// The that represents the asynchronous operation, returning true if the specified + /// has a confirmed + /// telephone number otherwise false. + /// + Task IsPhoneNumberConfirmedAsync(User user); + + /// + /// Generates a telephone number change token for the specified user. + /// + /// The user to generate a telephone number token for. + /// The new phone number the validation token should be sent to. + /// + /// The that represents the asynchronous operation, containing the telephone change number token. + /// + Task GenerateChangePhoneNumberTokenAsync(User user, string phoneNumber); + + /// + /// Returns a flag indicating whether the specified 's phone number change verification + /// token is valid for the given . + /// + /// The user to validate the token against. + /// The telephone number change token to validate. + /// The telephone number the token was generated for. + /// + /// The that represents the asynchronous operation, returning true if the + /// is valid, otherwise false. + /// + Task VerifyChangePhoneNumberTokenAsync(User user, string token, string phoneNumber); + + /// + /// Returns a flag indicating whether the specified is valid for + /// the given and . + /// + /// The user to validate the token against. + /// The token provider used to generate the token. + /// The purpose the token should be generated for. + /// The token to validate + /// + /// The that represents the asynchronous operation, returning true if the + /// is valid, otherwise false. + /// + Task VerifyUserTokenAsync(User user, string tokenProvider, string purpose, string token); + + /// + /// Generates a token for the given and . + /// + /// The user the token will be for. + /// The provider which will generate the token. + /// The purpose the token will be for. + /// + /// The that represents result of the asynchronous operation, a token for + /// the given user and purpose. + /// + Task GenerateUserTokenAsync(User user, string tokenProvider, string purpose); + + /// + /// Registers a token provider. + /// + /// The name of the provider to register. + /// The provider to register. + void RegisterTokenProvider(string providerName, IUserTwoFactorTokenProvider provider); + + /// + /// Gets a list of valid two factor token providers for the specified , + /// as an asynchronous operation. + /// + /// The user the whose two factor authentication providers will be returned. + /// + /// The that represents result of the asynchronous operation, a list of two + /// factor authentication providers for the specified user. + /// + Task> GetValidTwoFactorProvidersAsync(User user); + + /// + /// Verifies the specified two factor authentication against the . + /// + /// The user the token is supposed to be for. + /// The provider which will verify the token. + /// The token to verify. + /// + /// The that represents result of the asynchronous operation, true if the token is valid, + /// otherwise false. + /// + Task VerifyTwoFactorTokenAsync(User user, string tokenProvider, string token); + + /// + /// Gets a two factor authentication token for the specified . + /// + /// The user the token is for. + /// The provider which will generate the token. + /// + /// The that represents result of the asynchronous operation, a two factor authentication token + /// for the user. + /// + Task GenerateTwoFactorTokenAsync(User user, string tokenProvider); + + /// + /// Returns a flag indicating whether the specified has two factor authentication enabled or + /// not, + /// as an asynchronous operation. + /// + /// The user whose two factor authentication enabled status should be retrieved. + /// + /// The that represents the asynchronous operation, true if the specified + /// has two factor authentication enabled, otherwise false. + /// + Task GetTwoFactorEnabledAsync(User user); + + /// + /// Sets a flag indicating whether the specified has two factor authentication enabled or not, + /// as an asynchronous operation. + /// + /// The user whose two factor authentication enabled status should be set. + /// + /// A flag indicating whether the specified has two factor authentication + /// enabled. + /// + /// + /// The that represents the asynchronous operation, the of the + /// operation + /// + Task SetTwoFactorEnabledAsync(User user, bool enabled); + + /// + /// Returns a flag indicating whether the specified his locked out, + /// as an asynchronous operation. + /// + /// The user whose locked out status should be retrieved. + /// + /// The that represents the asynchronous operation, true if the specified + /// is locked out, otherwise false. + /// + Task IsLockedOutAsync(User user); + + /// + /// Sets a flag indicating whether the specified is locked out, + /// as an asynchronous operation. + /// + /// The user whose locked out status should be set. + /// Flag indicating whether the user is locked out or not. + /// + /// The that represents the asynchronous operation, the of the + /// operation + /// + Task SetLockoutEnabledAsync(User user, bool enabled); + + /// + /// Retrieves a flag indicating whether user lockout can enabled for the specified user. + /// + /// The user whose ability to be locked out should be returned. + /// + /// The that represents the asynchronous operation, true if a user can be locked out, otherwise + /// false. + /// + Task GetLockoutEnabledAsync(User user); + + /// + /// Gets the last a user's last lockout expired, if any. + /// Any time in the past should be indicates a user is not locked out. + /// + /// The user whose lockout date should be retrieved. + /// + /// A that represents the lookup, a containing the last time + /// a user's lockout expired, if any. + /// + Task GetLockoutEndDateAsync(User user); + + /// + /// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a + /// user. + /// + /// The user whose lockout date should be set. + /// + /// The after which the 's lockout should + /// end. + /// + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task SetLockoutEndDateAsync(User user, DateTimeOffset? lockoutEnd); + + /// + /// Increments the access failed count for the user as an asynchronous operation. + /// If the failed access account is greater than or equal to the configured maximum number of attempts, + /// the user will be locked out for the configured lockout time span. + /// + /// The user whose failed access count to increment. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task AccessFailedAsync(User user); + + /// + /// Resets the access failed count for the specified . + /// + /// The user whose failed access count should be reset. + /// + /// The that represents the asynchronous operation, containing the + /// of the operation. + /// + Task ResetAccessFailedCountAsync(User user); + + /// + /// Retrieves the current number of failed accesses for the given . + /// + /// The user whose access failed count should be retrieved for. + /// + /// The that contains the result the asynchronous operation, the current failed access count + /// for the user. + /// + Task GetAccessFailedCountAsync(User user); + + /// + /// Returns a list of users from the user store who have the specified . + /// + /// The claim to look for. + /// + /// A that represents the result of the asynchronous query, a list of + /// TUsers who have the specified claim. + /// + Task> GetUsersForClaimAsync(Claim claim); + + /// + /// Returns a list of users from the user store who are members of the specified . + /// + /// The name of the role whose users should be returned. + /// + /// A that represents the result of the asynchronous query, a list of + /// TUsers who are members of the specified role. + /// + Task> GetUsersInRoleAsync(string roleName); + + /// + /// Returns an authentication token for a user. + /// + /// + /// The authentication scheme for the provider the token is associated with. + /// The name of the token. + /// The authentication token for a user + Task GetAuthenticationTokenAsync(User user, string loginProvider, string tokenName); + + /// + /// Sets an authentication token for a user. + /// + /// + /// The authentication scheme for the provider the token is associated with. + /// The name of the token. + /// The value of the token. + /// Whether the user was successfully updated. + Task SetAuthenticationTokenAsync(User user, string loginProvider, string tokenName, + string tokenValue); + + /// + /// Remove an authentication token for a user. + /// + /// + /// The authentication scheme for the provider the token is associated with. + /// The name of the token. + /// Whether a token was removed. + Task RemoveAuthenticationTokenAsync(User user, string loginProvider, string tokenName); + + /// + /// Returns the authenticator key for the user. + /// + /// The user. + /// The authenticator key + Task GetAuthenticatorKeyAsync(User user); + + /// + /// Resets the authenticator key for the user. + /// + /// The user. + /// Whether the user was successfully updated. + Task ResetAuthenticatorKeyAsync(User user); + + /// + /// Generates a new base32 encoded 160-bit security secret (size of SHA1 hash). + /// + /// The new security secret. + string GenerateNewAuthenticatorKey(); + + /// + /// Generates recovery codes for the user, this invalidates any previous recovery codes for the user. + /// + /// The user to generate recovery codes for. + /// The number of codes to generate. + /// + /// The new recovery codes for the user. Note: there may be less than number returned, as duplicates will be + /// removed. + /// + Task> GenerateNewTwoFactorRecoveryCodesAsync(User user, int number); + + /// + /// Generate a new recovery code. + /// + /// + string CreateTwoFactorRecoveryCode(); + + /// + /// Returns whether a recovery code is valid for a user. Note: recovery codes are only valid + /// once, and will be invalid after use. + /// + /// The user who owns the recovery code. + /// The recovery code to use. + /// True if the recovery code was found for the user. + Task RedeemTwoFactorRecoveryCodeAsync(User user, string code); + + /// + /// Returns how many recovery code are still valid for a user. + /// + /// The user. + /// How many recovery code are still valid for a user. + Task CountRecoveryCodesAsync(User user); + + /// + /// Creates bytes to use as a security token from the user's security stamp. + /// + /// The user. + /// The security token bytes. + Task CreateSecurityTokenAsync(User user); + + /// + /// Updates a user's password hash. + /// + /// The user. + /// The new password. + /// Whether to validate the password. + /// Whether the password has was successfully updated. + Task UpdatePasswordHash(User user, string newPassword, bool validatePassword); + + #endregion + + #region CustomMethods + + User FindById(int userId); + + Task> GetAllUsersAsync(); + + /// + /// Returns the current user corresponding or null. + /// + /// Returns the current user corresponding or null. + User GetCurrentUser(); + + /// + /// Returns the current user corresponding or null. + /// + /// Returns the current user corresponding or null. + Task GetCurrentUserAsync(); + + /// + /// Returns the current User ID claim value if present otherwise returns null. + /// + /// The current User ID claim value, or null if the claim is not present. + string GetCurrentUserId(); + + /// + /// Returns the current User ID claim value if present otherwise returns null. + /// + int? GetCurrentIntUserId(); + + /// + /// Returns the current user Name claim value if present otherwise returns null. + /// + /// The current user Name claim value, or null if the claim is not present. + string GetCurrentUserName(); + + Task HasPhoneNumberAsync(int userId); + + Task GetEmailImageAsync(int? userId); + + Task GetPagedUsersListAsync( + int pageNumber, int recordsPerPage, + string sortByField, SortOrder sortOrder, + bool showAllUsers); + + Task GetPagedUsersListAsync(SearchUsersViewModel model, int pageNumber); + + Task FindByIdIncludeUserRolesAsync(int userId); + + Task UpdateUserAndSecurityStampAsync(int userId, Action action); + + Task AddOrUpdateUserRolesAsync(int userId, IList selectedRoleIds, Action action = null); + + #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationUserStore.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationUserStore.cs index 934bcc3..0fe1249 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationUserStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IApplicationUserStore.cs @@ -1,291 +1,285 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; +using System.Security.Claims; using ASPNETCoreIdentitySample.Entities.Identity; using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface IApplicationUserStore : IDisposable { - public interface IApplicationUserStore : IDisposable - { - #region BaseClass - - /// - /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. - /// - /// - /// True if changes should be automatically persisted, otherwise false. - /// - bool AutoSaveChanges { get; set; } - - /// - /// Creates the specified in the user store. - /// - /// The user to create. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, containing the of the creation operation. - Task CreateAsync(User user, CancellationToken cancellationToken = default); - - /// - /// Updates the specified in the user store. - /// - /// The user to update. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, containing the of the update operation. - Task UpdateAsync(User user, CancellationToken cancellationToken = default); - - /// - /// Deletes the specified from the user store. - /// - /// The user to delete. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation, containing the of the update operation. - Task DeleteAsync(User user, CancellationToken cancellationToken = default); - - /// - /// Finds and returns a user, if any, who has the specified . - /// - /// The user ID to search for. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The that represents the asynchronous operation, containing the user matching the specified if it exists. - /// - Task FindByIdAsync(string userId, CancellationToken cancellationToken = default); - - /// - /// Finds and returns a user, if any, who has the specified normalized user name. - /// - /// The normalized user name to search for. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The that represents the asynchronous operation, containing the user matching the specified if it exists. - /// - Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default); - - /// - /// A navigation property for the users the store contains. - /// - IQueryable Users { get; } - - /// - /// Return a role with the normalized name if it exists. - /// - /// The normalized role name. - /// The used to propagate notifications that the operation should be canceled. - /// The role if it exists. - Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken); - - /// - /// Return a user role for the userId and roleId if it exists. - /// - /// The user's id. - /// The role's id. - /// The used to propagate notifications that the operation should be canceled. - /// The user role if it exists. - Task FindUserRoleAsync(int userId, int roleId, CancellationToken cancellationToken); - - /// - /// Return a user with the matching userId if it exists. - /// - /// The user's id. - /// The used to propagate notifications that the operation should be canceled. - /// The user if it exists. - Task FindUserAsync(int userId, CancellationToken cancellationToken); - - /// - /// Return a user login with the matching userId, provider, providerKey if it exists. - /// - /// The user's id. - /// The login provider name. - /// The key provided by the to identify a user. - /// The used to propagate notifications that the operation should be canceled. - /// The user login if it exists. - Task FindUserLoginAsync(int userId, string loginProvider, string providerKey, CancellationToken cancellationToken); - - /// - /// Return a user login with provider, providerKey if it exists. - /// - /// The login provider name. - /// The key provided by the to identify a user. - /// The used to propagate notifications that the operation should be canceled. - /// The user login if it exists. - Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken); - - /// - /// Adds the given to the specified . - /// - /// The user to add the role to. - /// The role to add. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task AddToRoleAsync(User user, string normalizedRoleName, CancellationToken cancellationToken = default); - - /// - /// Removes the given from the specified . - /// - /// The user to remove the role from. - /// The role to remove. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task RemoveFromRoleAsync(User user, string normalizedRoleName, CancellationToken cancellationToken = default); - - /// - /// Retrieves the roles the specified is a member of. - /// - /// The user whose roles should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the roles the user is a member of. - Task> GetRolesAsync(User user, CancellationToken cancellationToken = default); - - /// - /// Returns a flag indicating if the specified user is a member of the give . - /// - /// The user whose role membership should be checked. - /// The role to check membership of - /// The used to propagate notifications that the operation should be canceled. - /// A containing a flag indicating if the specified user is a member of the given group. If the - /// user is a member of the group the returned value with be true, otherwise it will be false. - Task IsInRoleAsync(User user, string normalizedRoleName, CancellationToken cancellationToken = default); - - /// - /// Get the claims associated with the specified as an asynchronous operation. - /// - /// The user whose claims should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// A that contains the claims granted to a user. - Task> GetClaimsAsync(User user, CancellationToken cancellationToken = default); - - /// - /// Adds the given to the specified . - /// - /// The user to add the claim to. - /// The claim to add to the user. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task AddClaimsAsync(User user, IEnumerable claims, CancellationToken cancellationToken = default); - - /// - /// Replaces the on the specified , with the . - /// - /// The user to replace the claim on. - /// The claim replace. - /// The new claim replacing the . - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task ReplaceClaimAsync(User user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default); - - /// - /// Removes the given from the specified . - /// - /// The user to remove the claims from. - /// The claim to remove. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task RemoveClaimsAsync(User user, IEnumerable claims, CancellationToken cancellationToken = default); - - /// - /// Adds the given to the specified . - /// - /// The user to add the login to. - /// The login to add to the user. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task AddLoginAsync(User user, UserLoginInfo login, CancellationToken cancellationToken = default); - - /// - /// Removes the given from the specified . - /// - /// The user to remove the login from. - /// The login to remove from the user. - /// The key provided by the to identify a user. - /// The used to propagate notifications that the operation should be canceled. - /// The that represents the asynchronous operation. - Task RemoveLoginAsync(User user, string loginProvider, string providerKey, CancellationToken cancellationToken = default); - - /// - /// Retrieves the associated logins for the specified . - /// - /// The user whose associated logins to retrieve. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The for the asynchronous operation, containing a list of for the specified , if any. - /// - Task> GetLoginsAsync(User user, CancellationToken cancellationToken = default); - - /// - /// Retrieves the user associated with the specified login provider and login provider key. - /// - /// The login provider who provided the . - /// The key provided by the to identify a user. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The for the asynchronous operation, containing the user, if any which matched the specified login provider and key. - /// - Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default); - - /// - /// Gets the user, if any, associated with the specified, normalized email address. - /// - /// The normalized email address to return the user for. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The task object containing the results of the asynchronous lookup operation, the user if any associated with the specified normalized email address. - /// - Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default); - - /// - /// Retrieves all users with the specified claim. - /// - /// The claim whose users should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The contains a list of users, if any, that contain the specified claim. - /// - Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default); - - /// - /// Retrieves all users in the specified role. - /// - /// The role whose users should be retrieved. - /// The used to propagate notifications that the operation should be canceled. - /// - /// The contains a list of users, if any, that are in the specified role. - /// - Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default); - - /// - /// Find a user token if it exists. - /// - /// The token owner. - /// The login provider for the token. - /// The name of the token. - /// The used to propagate notifications that the operation should be canceled. - /// The user token if it exists. - Task FindTokenAsync(User user, string loginProvider, string name, CancellationToken cancellationToken); - - /// - /// Add a new user token. - /// - /// The token to be added. - /// - Task AddUserTokenAsync(UserToken token); - - /// - /// Remove a new user token. - /// - /// The token to be removed. - /// - Task RemoveUserTokenAsync(UserToken token); - - #endregion - - #region CustomMethods - - // Add custom methods here - - #endregion - } + #region BaseClass + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + bool AutoSaveChanges { get; set; } + + /// + /// Creates the specified in the user store. + /// + /// The user to create. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the creation operation. + Task CreateAsync(User user, CancellationToken cancellationToken = default); + + /// + /// Updates the specified in the user store. + /// + /// The user to update. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the update operation. + Task UpdateAsync(User user, CancellationToken cancellationToken = default); + + /// + /// Deletes the specified from the user store. + /// + /// The user to delete. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the update operation. + Task DeleteAsync(User user, CancellationToken cancellationToken = default); + + /// + /// Finds and returns a user, if any, who has the specified . + /// + /// The user ID to search for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + Task FindByIdAsync(string userId, CancellationToken cancellationToken = default); + + /// + /// Finds and returns a user, if any, who has the specified normalized user name. + /// + /// The normalized user name to search for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default); + + /// + /// A navigation property for the users the store contains. + /// + IQueryable Users { get; } + + /// + /// Return a role with the normalized name if it exists. + /// + /// The normalized role name. + /// The used to propagate notifications that the operation should be canceled. + /// The role if it exists. + Task FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken); + + /// + /// Return a user role for the userId and roleId if it exists. + /// + /// The user's id. + /// The role's id. + /// The used to propagate notifications that the operation should be canceled. + /// The user role if it exists. + Task FindUserRoleAsync(int userId, int roleId, CancellationToken cancellationToken); + + /// + /// Return a user with the matching userId if it exists. + /// + /// The user's id. + /// The used to propagate notifications that the operation should be canceled. + /// The user if it exists. + Task FindUserAsync(int userId, CancellationToken cancellationToken); + + /// + /// Return a user login with the matching userId, provider, providerKey if it exists. + /// + /// The user's id. + /// The login provider name. + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// The user login if it exists. + Task FindUserLoginAsync(int userId, string loginProvider, string providerKey, CancellationToken cancellationToken); + + /// + /// Return a user login with provider, providerKey if it exists. + /// + /// The login provider name. + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// The user login if it exists. + Task FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken); + + /// + /// Adds the given to the specified . + /// + /// The user to add the role to. + /// The role to add. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + Task AddToRoleAsync(User user, string normalizedRoleName, CancellationToken cancellationToken = default); + + /// + /// Removes the given from the specified . + /// + /// The user to remove the role from. + /// The role to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + Task RemoveFromRoleAsync(User user, string normalizedRoleName, CancellationToken cancellationToken = default); + + /// + /// Retrieves the roles the specified is a member of. + /// + /// The user whose roles should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the roles the user is a member of. + Task> GetRolesAsync(User user, CancellationToken cancellationToken = default); + + /// + /// Returns a flag indicating if the specified user is a member of the give . + /// + /// The user whose role membership should be checked. + /// The role to check membership of + /// The used to propagate notifications that the operation should be canceled. + /// A containing a flag indicating if the specified user is a member of the given group. If the + /// user is a member of the group the returned value with be true, otherwise it will be false. + Task IsInRoleAsync(User user, string normalizedRoleName, CancellationToken cancellationToken = default); + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The user whose claims should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the claims granted to a user. + Task> GetClaimsAsync(User user, CancellationToken cancellationToken = default); + + /// + /// Adds the given to the specified . + /// + /// The user to add the claim to. + /// The claim to add to the user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + Task AddClaimsAsync(User user, IEnumerable claims, CancellationToken cancellationToken = default); + + /// + /// Replaces the on the specified , with the . + /// + /// The user to replace the claim on. + /// The claim replace. + /// The new claim replacing the . + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + Task ReplaceClaimAsync(User user, Claim claim, Claim newClaim, CancellationToken cancellationToken = default); + + /// + /// Removes the given from the specified . + /// + /// The user to remove the claims from. + /// The claim to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + Task RemoveClaimsAsync(User user, IEnumerable claims, CancellationToken cancellationToken = default); + + /// + /// Adds the given to the specified . + /// + /// The user to add the login to. + /// The login to add to the user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + Task AddLoginAsync(User user, UserLoginInfo login, CancellationToken cancellationToken = default); + + /// + /// Removes the given from the specified . + /// + /// The user to remove the login from. + /// The login to remove from the user. + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + Task RemoveLoginAsync(User user, string loginProvider, string providerKey, CancellationToken cancellationToken = default); + + /// + /// Retrieves the associated logins for the specified . + /// + /// The user whose associated logins to retrieve. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The for the asynchronous operation, containing a list of for the specified , if any. + /// + Task> GetLoginsAsync(User user, CancellationToken cancellationToken = default); + + /// + /// Retrieves the user associated with the specified login provider and login provider key. + /// + /// The login provider who provided the . + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The for the asynchronous operation, containing the user, if any which matched the specified login provider and key. + /// + Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default); + + /// + /// Gets the user, if any, associated with the specified, normalized email address. + /// + /// The normalized email address to return the user for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous lookup operation, the user if any associated with the specified normalized email address. + /// + Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default); + + /// + /// Retrieves all users with the specified claim. + /// + /// The claim whose users should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The contains a list of users, if any, that contain the specified claim. + /// + Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken = default); + + /// + /// Retrieves all users in the specified role. + /// + /// The role whose users should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The contains a list of users, if any, that are in the specified role. + /// + Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default); + + /// + /// Find a user token if it exists. + /// + /// The token owner. + /// The login provider for the token. + /// The name of the token. + /// The used to propagate notifications that the operation should be canceled. + /// The user token if it exists. + Task FindTokenAsync(User user, string loginProvider, string name, CancellationToken cancellationToken); + + /// + /// Add a new user token. + /// + /// The token to be added. + /// + Task AddUserTokenAsync(UserToken token); + + /// + /// Remove a new user token. + /// + /// The token to be removed. + /// + Task RemoveUserTokenAsync(UserToken token); + + #endregion + + #region CustomMethods + + // Add custom methods here + + #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IEmailSender.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IEmailSender.cs index 3b4c710..20f3163 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IEmailSender.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IEmailSender.cs @@ -1,19 +1,16 @@ -using System.Threading.Tasks; +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +public interface IEmailSender { - public interface IEmailSender - { - #region BaseClass + #region BaseClass - Task SendEmailAsync(string email, string subject, string message); + Task SendEmailAsync(string email, string subject, string message); - #endregion + #endregion - #region CustomMethods + #region CustomMethods - Task SendEmailAsync(string email, string subject, string viewNameOrPath, T model); + Task SendEmailAsync(string email, string subject, string viewNameOrPath, T model); - #endregion - } + #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IIdentityDbInitializer.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IIdentityDbInitializer.cs index 43a30ac..ee649b4 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IIdentityDbInitializer.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IIdentityDbInitializer.cs @@ -1,21 +1,19 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface IIdentityDbInitializer { - public interface IIdentityDbInitializer - { - /// - /// Applies any pending migrations for the context to the database. - /// Will create the database if it does not already exist. - /// - void Initialize(); + /// + /// Applies any pending migrations for the context to the database. + /// Will create the database if it does not already exist. + /// + void Initialize(); - /// - /// Adds some default values to the IdentityDb - /// - void SeedData(); + /// + /// Adds some default values to the IdentityDb + /// + void SeedData(); - Task SeedDatabaseWithAdminUserAsync(); - } + Task SeedDatabaseWithAdminUserAsync(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISecurityTrimmingService.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISecurityTrimmingService.cs index 3b088a2..abf0c4b 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISecurityTrimmingService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISecurityTrimmingService.cs @@ -1,10 +1,9 @@ using System.Security.Claims; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface ISecurityTrimmingService { - public interface ISecurityTrimmingService - { - bool CanCurrentUserAccess(string area, string controller, string action); - bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action); - } + bool CanCurrentUserAccess(string area, string controller, string action); + bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISiteStatService.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISiteStatService.cs index 24f2977..aac54a5 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISiteStatService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISiteStatService.cs @@ -1,19 +1,16 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.Entities.Identity; using System.Security.Claims; using ASPNETCoreIdentitySample.ViewModels.Identity; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface ISiteStatService { - public interface ISiteStatService - { - Task> GetOnlineUsersListAsync(int numbersToTake, int minutesToTake); + Task> GetOnlineUsersListAsync(int numbersToTake, int minutesToTake); - Task> GetTodayBirthdayListAsync(); + Task> GetTodayBirthdayListAsync(); - Task UpdateUserLastVisitDateTimeAsync(ClaimsPrincipal claimsPrincipal); + Task UpdateUserLastVisitDateTimeAsync(ClaimsPrincipal claimsPrincipal); - Task GetUsersAverageAge(); - } + Task GetUsersAverageAge(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISmsSender.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISmsSender.cs index e3a10b4..43513d5 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISmsSender.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/ISmsSender.cs @@ -1,17 +1,14 @@ -using System.Threading.Tasks; +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +public interface ISmsSender { - public interface ISmsSender - { - #region BaseClass + #region BaseClass - Task SendSmsAsync(string number, string message); + Task SendSmsAsync(string number, string message); - #endregion + #endregion - #region CustomMethods + #region CustomMethods - #endregion - } + #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IUsedPasswordsService.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IUsedPasswordsService.cs index 34d162c..4dad313 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IUsedPasswordsService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IUsedPasswordsService.cs @@ -1,14 +1,11 @@ -using System; -using System.Threading.Tasks; -using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface IUsedPasswordsService { - public interface IUsedPasswordsService - { - Task IsPreviouslyUsedPasswordAsync(User user, string newPassword); - Task AddToUsedPasswordsListAsync(User user); - Task IsLastUserPasswordTooOldAsync(int userId); - Task GetLastUserPasswordChangeDateAsync(int userId); - } + Task IsPreviouslyUsedPasswordAsync(User user, string newPassword); + Task AddToUsedPasswordsListAsync(User user); + Task IsLastUserPasswordTooOldAsync(int userId); + Task GetLastUserPasswordChangeDateAsync(int userId); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IUsersPhotoService.cs b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IUsersPhotoService.cs index d80573c..d9ac252 100644 --- a/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IUsersPhotoService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Contracts/Identity/IUsersPhotoService.cs @@ -1,13 +1,12 @@ using ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.Services.Contracts.Identity +namespace ASPNETCoreIdentitySample.Services.Contracts.Identity; + +public interface IUsersPhotoService { - public interface IUsersPhotoService - { - string GetUsersAvatarsFolderPath(); - void SetUserDefaultPhoto(User user); - string GetUserDefaultPhoto(string photoFileName); - string GetUserPhotoUrl(string photoFileName); - string GetCurrentUserPhotoUrl(); - } + string GetUsersAvatarsFolderPath(); + void SetUserDefaultPhoto(User user); + string GetUserDefaultPhoto(string photoFileName); + string GetUserPhotoUrl(string photoFileName); + string GetCurrentUserPhotoUrl(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/EfCategoryService.cs b/src/ASPNETCoreIdentitySample.Services/EfCategoryService.cs index 6d75aa9..648be0f 100644 --- a/src/ASPNETCoreIdentitySample.Services/EfCategoryService.cs +++ b/src/ASPNETCoreIdentitySample.Services/EfCategoryService.cs @@ -1,33 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using ASPNETCoreIdentitySample.DataLayer.Context; +using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.Entities; using ASPNETCoreIdentitySample.Services.Contracts; using Microsoft.EntityFrameworkCore; -namespace ASPNETCoreIdentitySample.Services +namespace ASPNETCoreIdentitySample.Services; + +public class EfCategoryService : ICategoryService { - public class EfCategoryService : ICategoryService - { - private readonly IUnitOfWork _uow; - private readonly DbSet _categories; + private readonly DbSet _categories; + private readonly IUnitOfWork _uow; - public EfCategoryService(IUnitOfWork uow) - { - _uow = uow ?? throw new ArgumentNullException(nameof(uow)); + public EfCategoryService(IUnitOfWork uow) + { + _uow = uow ?? throw new ArgumentNullException(nameof(uow)); - _categories = _uow.Set(); - } + _categories = _uow.Set(); + } - public void AddNewCategory(Category category) - { - _categories.Add(category); - } + public void AddNewCategory(Category category) + { + _uow.Set().Add(category); + } - public IList GetAllCategories() - { - return _categories.ToList(); - } + public IList GetAllCategories() + { + return _categories.ToList(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/EfProductService.cs b/src/ASPNETCoreIdentitySample.Services/EfProductService.cs index 1006f7f..92f3adb 100644 --- a/src/ASPNETCoreIdentitySample.Services/EfProductService.cs +++ b/src/ASPNETCoreIdentitySample.Services/EfProductService.cs @@ -1,32 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using ASPNETCoreIdentitySample.DataLayer.Context; +using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.Entities; using ASPNETCoreIdentitySample.Services.Contracts; using Microsoft.EntityFrameworkCore; -namespace ASPNETCoreIdentitySample.Services +namespace ASPNETCoreIdentitySample.Services; + +public class EfProductService : IProductService { - public class EfProductService : IProductService - { - private readonly IUnitOfWork _uow; - private readonly DbSet _products; + private readonly DbSet _products; + private readonly IUnitOfWork _uow; - public EfProductService(IUnitOfWork uow) - { - _uow = uow ?? throw new ArgumentNullException(nameof(_uow)); - _products = _uow.Set(); - } + public EfProductService(IUnitOfWork uow) + { + _uow = uow ?? throw new ArgumentNullException(nameof(uow)); + _products = _uow.Set(); + } - public void AddNewProduct(Product product) - { - _products.Add(product); - } + public void AddNewProduct(Product product) + { + _uow.Set().Add(product); + } - public IList GetAllProducts() - { - return _products.Include(x => x.Category).ToList(); - } + public IList GetAllProducts() + { + return _products.Include(x => x.Category).ToList(); } -} +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsPrincipalFactory.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsPrincipalFactory.cs index b2fba77..5025ed8 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsPrincipalFactory.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsPrincipalFactory.cs @@ -1,53 +1,69 @@ -using ASPNETCoreIdentitySample.Entities.Identity; +using System.Security.Claims; +using System.Security.Principal; +using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; -using System; -using System.Security.Claims; -using System.Security.Principal; -using System.Threading.Tasks; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// Customizing claims transformation in ASP.NET Core Identity +/// More info: http://www.dntips.ir/post/2580 +/// +public class ApplicationClaimsPrincipalFactory : UserClaimsPrincipalFactory { - /// - /// Customizing claims transformation in ASP.NET Core Identity - /// More info: http://www.dotnettips.info/post/2580 - /// - public class ApplicationClaimsPrincipalFactory : UserClaimsPrincipalFactory - { - public static readonly string PhotoFileName = nameof(PhotoFileName); + public static readonly string PhotoFileName = nameof(PhotoFileName); - private readonly IOptions _optionsAccessor; - private readonly IApplicationRoleManager _roleManager; - private readonly IApplicationUserManager _userManager; + 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 ApplicationClaimsPrincipalFactory( - IApplicationUserManager userManager, - IApplicationRoleManager roleManager, - IOptions optionsAccessor) - : base((UserManager)userManager, (RoleManager)roleManager, optionsAccessor) + public override async Task CreateAsync(User user) + { + if (user == null) { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - _optionsAccessor = optionsAccessor ?? throw new ArgumentNullException(nameof(optionsAccessor)); + throw new ArgumentNullException(nameof(user)); } - public override async Task CreateAsync(User user) + var + 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) + { + if (user == null) { - var 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; + throw new ArgumentNullException(nameof(user)); } - private static void addCustomClaims(User user, IPrincipal principal) + if (principal == null) { - ((ClaimsIdentity)principal.Identity).AddClaims(new[] - { - new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), 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), - }); + throw new ArgumentNullException(nameof(principal)); } + + ((ClaimsIdentity)principal.Identity)?.AddClaims(new[] + { + 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 2755811..0f9aa16 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsTransformation.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationClaimsTransformation.cs @@ -1,107 +1,109 @@ -using System; -using System.Collections.Generic; using System.Security.Claims; using System.Security.Principal; -using System.Threading.Tasks; using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// To register it: services.AddScoped(IClaimsTransformation, ApplicationClaimsTransformation)(); +/// 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 { - /// - /// To register it: services.AddScoped(); - /// How to add existing db user's claims to the user's active directory claims. - /// More info: http://www.dotnettips.info/post/2762 - /// - public class ApplicationClaimsTransformation : 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)); + } + + public async Task TransformAsync(ClaimsPrincipal principal) { - private readonly IApplicationUserManager _userManager; - private readonly IApplicationRoleManager _roleManager; - private readonly ILogger _logger; - - public ApplicationClaimsTransformation( - IApplicationUserManager userManager, - IApplicationRoleManager roleManager, - ILogger logger) + if (principal == null) { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + throw new ArgumentNullException(nameof(principal)); } - public async Task TransformAsync(ClaimsPrincipal principal) + if (!(principal.Identity is ClaimsIdentity identity) || !IsNtlm(identity)) { - if (!(principal.Identity is ClaimsIdentity identity) || !isNTLM(identity)) - { - return principal; - } - - var claims = await addExistingUserClaimsAsync(identity); - identity.AddClaims(claims); - return principal; } - private async Task> addExistingUserClaimsAsync(IIdentity identity) + var claims = await AddExistingUserClaimsAsync(identity); + identity.AddClaims(claims); + + return principal; + } + + private async Task> AddExistingUserClaimsAsync(IIdentity identity) + { + var claims = new List(); + var user = await _userManager.Users.Include(u => u.Claims) + .FirstOrDefaultAsync(u => u.UserName == identity.Name) + ; + if (user == null) { - var claims = new List(); - var user = await _userManager.Users.Include(u => u.Claims) - .FirstOrDefaultAsync(u => u.UserName == identity.Name) - ; - if (user == null) - { - _logger.LogError($"Couldn't find {identity.Name}."); - return claims; - } + _logger.LogErrorMessage($"Couldn't find {identity.Name}."); + return claims; + } - var Options = new ClaimsIdentityOptions(); + var options = new ClaimsIdentityOptions(); - claims.Add(new Claim(Options.UserIdClaimType, user.Id.ToString())); - claims.Add(new Claim(Options.UserNameClaimType, user.UserName)); + claims.Add(new Claim(options.UserIdClaimType, user.Id.ToString(CultureInfo.InvariantCulture))); + claims.Add(new Claim(options.UserNameClaimType, user.UserName)); - if (_userManager.SupportsUserSecurityStamp) - { - claims.Add(new Claim(Options.SecurityStampClaimType, - await _userManager.GetSecurityStampAsync(user))); - } + if (_userManager.SupportsUserSecurityStamp) + { + claims.Add(new Claim(options.SecurityStampClaimType, + await _userManager.GetSecurityStampAsync(user))); + } - if (_userManager.SupportsUserClaim) - { - claims.AddRange(await _userManager.GetClaimsAsync(user)); - } + if (_userManager.SupportsUserClaim) + { + claims.AddRange(await _userManager.GetClaimsAsync(user)); + } - if (_userManager.SupportsUserRole) + if (_userManager.SupportsUserRole) + { + var roles = await _userManager.GetRolesAsync(user); + foreach (var roleName in roles) { - var roles = await _userManager.GetRolesAsync(user); - foreach (var roleName in roles) - { - claims.Add(new Claim(Options.RoleClaimType, roleName)); + claims.Add(new Claim(options.RoleClaimType, roleName)); - if (isNTLM(identity)) - { - claims.Add(new Claim(ClaimTypes.GroupSid, roleName)); - } + if (IsNtlm(identity)) + { + claims.Add(new Claim(ClaimTypes.GroupSid, roleName)); + } - if (_roleManager.SupportsRoleClaims) + if (_roleManager.SupportsRoleClaims) + { + var role = await _roleManager.FindByNameAsync(roleName); + if (role != null) { - var role = await _roleManager.FindByNameAsync(roleName); - if (role != null) - { - claims.AddRange(await _roleManager.GetClaimsAsync(role)); - } + claims.AddRange(await _roleManager.GetClaimsAsync(role)); } } } - - return claims; } - private static bool isNTLM(IIdentity identity) - { - return identity.AuthenticationType == "NTLM"; - } + return claims; + } + + private static bool IsNtlm(IIdentity identity) + { + return string.Equals(identity.AuthenticationType, "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 01d65bd..4e8fda8 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleManager.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleManager.cs @@ -2,240 +2,238 @@ using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System; -using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2578 +/// +public class ApplicationRoleManager : + RoleManager, + IApplicationRoleManager { - /// - /// More info: http://www.dotnettips.info/post/2578 - /// - public class ApplicationRoleManager : - RoleManager, - IApplicationRoleManager + private readonly IHttpContextAccessor _contextAccessor; + private readonly IdentityErrorDescriber _errors; + private readonly ILookupNormalizer _keyNormalizer; + private readonly ILogger _logger; + private readonly IEnumerable> _roleValidators; + private readonly IApplicationRoleStore _store; + private readonly IUnitOfWork _uow; + private readonly DbSet _users; + + public ApplicationRoleManager( + IApplicationRoleStore store, + IEnumerable> roleValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + ILogger logger, + IHttpContextAccessor contextAccessor, + IUnitOfWork uow) : + base((RoleStore)store, roleValidators, keyNormalizer, + errors, logger) { - private readonly IHttpContextAccessor _contextAccessor; - private readonly IUnitOfWork _uow; - private readonly IdentityErrorDescriber _errors; - private readonly ILookupNormalizer _keyNormalizer; - private readonly ILogger _logger; - private readonly IEnumerable> _roleValidators; - private readonly IApplicationRoleStore _store; - private readonly DbSet _users; - - public ApplicationRoleManager( - IApplicationRoleStore store, - IEnumerable> roleValidators, - ILookupNormalizer keyNormalizer, - IdentityErrorDescriber errors, - ILogger logger, - IHttpContextAccessor contextAccessor, - IUnitOfWork uow) : - base((RoleStore)store, roleValidators, keyNormalizer, errors, logger) - { - _store = store ?? throw new ArgumentNullException(nameof(store)); - _roleValidators = roleValidators ?? throw new ArgumentNullException(nameof(roleValidators)); - _keyNormalizer = keyNormalizer ?? throw new ArgumentNullException(nameof(keyNormalizer)); - _errors = errors ?? throw new ArgumentNullException(nameof(errors)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); - _uow = uow ?? throw new ArgumentNullException(nameof(uow)); - _users = _uow.Set(); - } - - #region BaseClass - - #endregion - - #region CustomMethods + _store = store ?? throw new ArgumentNullException(nameof(store)); + _roleValidators = roleValidators ?? throw new ArgumentNullException(nameof(roleValidators)); + _keyNormalizer = keyNormalizer ?? throw new ArgumentNullException(nameof(keyNormalizer)); + _errors = errors ?? throw new ArgumentNullException(nameof(errors)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); + _uow = uow ?? throw new ArgumentNullException(nameof(uow)); + _users = _uow.Set(); + } - public IList FindCurrentUserRoles() - { - var userId = getCurrentUserId(); - return FindUserRoles(userId); - } + #region BaseClass - public IList FindUserRoles(int userId) - { - var userRolesQuery = from role in Roles - from user in role.Users - where user.UserId == userId - select role; + #endregion - return userRolesQuery.OrderBy(x => x.Name).ToList(); - } + #region CustomMethods - public Task> GetAllCustomRolesAsync() - { - return Roles.ToListAsync(); - } + public IList FindCurrentUserRoles() + { + var userId = GetCurrentUserId(); + return FindUserRoles(userId); + } - public IList GetAllCustomRolesAndUsersCountList() - { - return Roles.Select(role => - new RoleAndUsersCountViewModel - { - Role = role, - UsersCount = role.Users.Count() - }).ToList(); - } + public IList FindUserRoles(int userId) + { + var userRolesQuery = from role in Roles + from user in role.Users + where user.UserId == userId + select role; - public async Task GetPagedApplicationUsersInRoleListAsync( - int roleId, - int pageNumber, int recordsPerPage, - string sortByField, SortOrder sortOrder, - bool showAllUsers) - { - var skipRecords = pageNumber * recordsPerPage; + return userRolesQuery.OrderBy(x => x.Name).ToList(); + } - 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(); + public Task> GetAllCustomRolesAsync() + { + return Roles.ToListAsync(); + } - if (!showAllUsers) + public IList GetAllCustomRolesAndUsersCountList() + { + return Roles.Select(role => + new RoleAndUsersCountViewModel { - query = query.Where(x => x.IsActive); - } + Role = role, + UsersCount = role.Users.Count() + }).ToList(); + } - switch (sortByField) - { - case nameof(User.Id): - query = sortOrder == SortOrder.Descending ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id); - break; - default: - query = sortOrder == SortOrder.Descending ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id); - break; - } + public async Task GetPagedApplicationUsersInRoleListAsync( + int roleId, + int pageNumber, int recordsPerPage, + string sortByField, SortOrder sortOrder, + bool showAllUsers) + { + var skipRecords = pageNumber * recordsPerPage; - return new PagedUsersListViewModel - { - Paging = - { - TotalItems = await query.CountAsync() - }, - Users = await query.Skip(skipRecords).Take(recordsPerPage).ToListAsync(), - Roles = await Roles.ToListAsync() - }; - } + 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(); - public IList GetApplicationUsersInRole(string roleName) + if (!showAllUsers) { - var roleUserIdsQuery = from role in Roles - where role.Name == roleName - from user in role.Users - select user.UserId; - return _users.Where(applicationUser => roleUserIdsQuery.Contains(applicationUser.Id)) - .ToList(); + query = query.Where(x => x.IsActive); } - public IList GetRolesForCurrentUser() + switch (sortByField) { - var userId = getCurrentUserId(); - return GetRolesForUser(userId); + default: + query = sortOrder == SortOrder.Descending + ? query.OrderByDescending(x => x.Id) + : query.OrderBy(x => x.Id); + break; } - public IList GetRolesForUser(int userId) + return new PagedUsersListViewModel { - var roles = FindUserRoles(userId); - if (roles == null || !roles.Any()) + Paging = { - return new List(); - } + TotalItems = await query.CountAsync() + }, + Users = await query.Skip(skipRecords).Take(recordsPerPage).ToListAsync(), + Roles = await Roles.ToListAsync() + }; + } - return roles.ToList(); - } + 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(); + } - public IList GetUserRolesInRole(string roleName) - { - return Roles.Where(role => role.Name == roleName) - .SelectMany(role => role.Users) - .ToList(); - } + public IList GetRolesForCurrentUser() + { + var userId = GetCurrentUserId(); + return GetRolesForUser(userId); + } - public bool IsCurrentUserInRole(string roleName) + public IList GetRolesForUser(int userId) + { + var roles = FindUserRoles(userId); + if (roles == null || !roles.Any()) { - var userId = getCurrentUserId(); - return IsUserInRole(userId, roleName); + return new List(); } - 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; - var userRole = userRolesQuery.FirstOrDefault(); - return userRole != null; - } + return roles.ToList(); + } - public Task FindRoleIncludeRoleClaimsAsync(int roleId) - { - return Roles.Include(x => x.Claims).FirstOrDefaultAsync(x => x.Id == roleId); - } + public IList GetUserRolesInRole(string roleName) + { + return Roles.Where(role => role.Name == roleName) + .SelectMany(role => role.Users) + .ToList(); + } - public async Task AddOrUpdateRoleClaimsAsync( - int roleId, - string roleClaimType, - IList selectedRoleClaimValues) + 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; + var userRole = userRolesQuery.FirstOrDefault(); + return userRole != null; + } + + public Task FindRoleIncludeRoleClaimsAsync(int roleId) + { + return Roles.Include(x => x.Claims).FirstOrDefaultAsync(x => x.Id == roleId); + } + + public async Task AddOrUpdateRoleClaimsAsync( + int roleId, + string roleClaimType, + IList selectedRoleClaimValues) + { + var role = await FindRoleIncludeRoleClaimsAsync(roleId); + if (role == null) { - var role = await FindRoleIncludeRoleClaimsAsync(roleId); - if (role == null) + return IdentityResult.Failed(new IdentityError { - return IdentityResult.Failed(new IdentityError - { - Code = "RoleNotFound", - Description = "نقش مورد نظر یافت نشد." - }); - } + Code = "RoleNotFound", + Description = "نقش مورد نظر یافت نشد." + }); + } - var currentRoleClaimValues = role.Claims.Where(roleClaim => roleClaim.ClaimType == roleClaimType) - .Select(roleClaim => roleClaim.ClaimValue) - .ToList(); + var currentRoleClaimValues = role.Claims.Where(roleClaim => + string.Equals(roleClaim.ClaimType, roleClaimType, StringComparison.Ordinal)) + .Select(roleClaim => roleClaim.ClaimValue) + .ToList(); - if (selectedRoleClaimValues == null) - { - selectedRoleClaimValues = new List(); - } - var newClaimValuesToAdd = selectedRoleClaimValues.Except(currentRoleClaimValues).ToList(); - foreach (var claimValue in newClaimValuesToAdd) + selectedRoleClaimValues ??= new List(); + + var newClaimValuesToAdd = selectedRoleClaimValues.Except(currentRoleClaimValues).ToList(); + foreach (var claimValue in newClaimValuesToAdd) + { + role.Claims.Add(new RoleClaim { - role.Claims.Add(new RoleClaim - { - RoleId = role.Id, - ClaimType = roleClaimType, - ClaimValue = claimValue - }); - } + RoleId = role.Id, + ClaimType = roleClaimType, + ClaimValue = claimValue + }); + } - var removedClaimValues = currentRoleClaimValues.Except(selectedRoleClaimValues).ToList(); - foreach (var claimValue in removedClaimValues) + 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)); + if (roleClaim != null) { - var roleClaim = role.Claims.SingleOrDefault(rc => rc.ClaimValue == claimValue && - rc.ClaimType == roleClaimType); - if (roleClaim != null) - { - role.Claims.Remove(roleClaim); - } + role.Claims.Remove(roleClaim); } - - return await UpdateAsync(role); } - private int getCurrentUserId() => _contextAccessor.HttpContext.User.Identity.GetUserId(); + return await UpdateAsync(role); + } - #endregion + private int GetCurrentUserId() + { + return _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 8f6a529..8141203 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationRoleStore.cs @@ -1,48 +1,56 @@ -using System; -using System.Security.Claims; +using System.Security.Claims; using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2578 +/// +public class ApplicationRoleStore : + RoleStore, + IApplicationRoleStore { - /// - /// More info: http://www.dotnettips.info/post/2578 - /// - public class ApplicationRoleStore : - RoleStore, - IApplicationRoleStore + private readonly IdentityErrorDescriber _describer; + private readonly IUnitOfWork _uow; + + public ApplicationRoleStore( + IUnitOfWork uow, + IdentityErrorDescriber describer) + : base((ApplicationDbContext)uow, describer) { - private readonly IUnitOfWork _uow; - private readonly IdentityErrorDescriber _describer; + _uow = uow ?? throw new ArgumentNullException(nameof(uow)); + _describer = describer ?? throw new ArgumentNullException(nameof(describer)); + } - public ApplicationRoleStore( - IUnitOfWork uow, - IdentityErrorDescriber describer) - : base((ApplicationDbContext)uow, describer) + #region BaseClass + + protected override RoleClaim CreateRoleClaim(Role role, Claim claim) + { + if (role == null) { - _uow = uow ?? throw new ArgumentNullException(nameof(_uow)); - _describer = describer ?? throw new ArgumentNullException(nameof(_describer)); + throw new ArgumentNullException(nameof(role)); } - #region BaseClass - - protected override RoleClaim CreateRoleClaim(Role role, Claim claim) + if (claim == null) { - return new RoleClaim - { - RoleId = role.Id, - ClaimType = claim.Type, - ClaimValue = claim.Value - }; + throw new ArgumentNullException(nameof(claim)); } - #endregion + return new RoleClaim + { + RoleId = role.Id, + ClaimType = claim.Type, + ClaimValue = claim.Value + }; + } - #region CustomMethods + #endregion - #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 b88be6c..916e07d 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationSignInManager.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationSignInManager.cs @@ -1,90 +1,89 @@ using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using System; -namespace ASPNETCoreIdentitySample.Services.Identity -{ - /// - /// More info: http://www.dotnettips.info/post/2578 - /// - public class ApplicationSignInManager : - SignInManager, - IApplicationSignInManager - { - private readonly IApplicationUserManager _userManager; - private readonly IHttpContextAccessor _contextAccessor; - private readonly IUserClaimsPrincipalFactory _claimsFactory; - private readonly IOptions _optionsAccessor; - private readonly ILogger _logger; - private readonly IAuthenticationSchemeProvider _schemes; - private readonly IUserConfirmation _confirmation; +namespace ASPNETCoreIdentitySample.Services.Identity; - 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; - } +/// +/// More info: http://www.dntips.ir/post/2578 +/// +public class ApplicationSignInManager : + SignInManager, + 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; - #region BaseClass + 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; + } - Task IApplicationSignInManager.IsLockedOut(User user) - { - return base.IsLockedOut(user); - } + #region BaseClass - Task IApplicationSignInManager.LockedOut(User user) - { - return base.LockedOut(user); - } + Task IApplicationSignInManager.IsLockedOut(User user) + { + return base.IsLockedOut(user); + } - Task IApplicationSignInManager.PreSignInCheck(User user) - { - return base.PreSignInCheck(user); - } + Task IApplicationSignInManager.LockedOut(User user) + { + return base.LockedOut(user); + } - Task IApplicationSignInManager.ResetLockout(User user) - { - return base.ResetLockout(user); - } + Task IApplicationSignInManager.PreSignInCheck(User user) + { + return base.PreSignInCheck(user); + } - Task IApplicationSignInManager.SignInOrTwoFactorAsync(User user, bool isPersistent, string loginProvider, bool bypassTwoFactor) - { - return base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); - } + Task IApplicationSignInManager.ResetLockout(User user) + { + return base.ResetLockout(user); + } - #endregion + Task IApplicationSignInManager.SignInOrTwoFactorAsync(User user, bool isPersistent, + string loginProvider, bool bypassTwoFactor) + { + return base.SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); + } - #region CustomMethods + #endregion - public bool IsCurrentUserSignedIn() - { - return IsSignedIn(_contextAccessor.HttpContext.User); - } + #region CustomMethods - public Task ValidateCurrentUserSecurityStampAsync() - { - return ValidateSecurityStampAsync(_contextAccessor.HttpContext.User); - } + public bool IsCurrentUserSignedIn() + { + return IsSignedIn(_contextAccessor.HttpContext?.User); + } - #endregion + public Task ValidateCurrentUserSecurityStampAsync() + { + return 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 0fd940f..6d3585d 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserManager.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserManager.cs @@ -1,412 +1,435 @@ -using ASPNETCoreIdentitySample.DataLayer.Context; +using System.Runtime.Versioning; +using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; +using DNTCommon.Web.Core; using DNTPersianUtils.Core; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System; -using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2578 +/// +public class ApplicationUserManager : + UserManager, + IApplicationUserManager { - /// - /// More info: http://www.dotnettips.info/post/2578 - /// - public class ApplicationUserManager : - UserManager, - 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; + + 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) { - private readonly IHttpContextAccessor _contextAccessor; - private readonly IUnitOfWork _uow; - private readonly IUsedPasswordsService _usedPasswordsService; - 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 IServiceProvider _services; - private readonly DbSet _users; - private readonly DbSet _roles; - private readonly IApplicationUserStore _userStore; - private readonly IEnumerable> _userValidators; - private User _currentUserInScope; - - 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(_userStore)); - _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(); - } + _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(); + } - #region BaseClass + #region BaseClass - string IApplicationUserManager.CreateTwoFactorRecoveryCode() - { - return base.CreateTwoFactorRecoveryCode(); - } + string IApplicationUserManager.CreateTwoFactorRecoveryCode() + { + return base.CreateTwoFactorRecoveryCode(); + } - Task IApplicationUserManager.VerifyPasswordAsync(IUserPasswordStore store, User user, string password) - { - return base.VerifyPasswordAsync(store, user, password); - } + Task IApplicationUserManager.VerifyPasswordAsync(IUserPasswordStore store, + User user, string password) + { + return base.VerifyPasswordAsync(store, user, password); + } - public override async Task CreateAsync(User user) + public override async Task CreateAsync(User user) + { + var result = await base.CreateAsync(user); + if (result.Succeeded) { - var result = await base.CreateAsync(user); - if (result.Succeeded) - { - await _usedPasswordsService.AddToUsedPasswordsListAsync(user); - } - return result; + await _usedPasswordsService.AddToUsedPasswordsListAsync(user); } - public override async Task CreateAsync(User user, string password) + return result; + } + + public override async Task CreateAsync(User user, string password) + { + var result = await base.CreateAsync(user, password); + if (result.Succeeded) { - var result = await base.CreateAsync(user, password); - if (result.Succeeded) - { - await _usedPasswordsService.AddToUsedPasswordsListAsync(user); - } - return result; + await _usedPasswordsService.AddToUsedPasswordsListAsync(user); } - public override async Task ChangePasswordAsync(User user, string currentPassword, string newPassword) + return result; + } + + public override async Task ChangePasswordAsync(User user, string currentPassword, + string newPassword) + { + var result = await base.ChangePasswordAsync(user, currentPassword, newPassword); + if (result.Succeeded) { - var result = await base.ChangePasswordAsync(user, currentPassword, newPassword); - if (result.Succeeded) - { - await _usedPasswordsService.AddToUsedPasswordsListAsync(user); - } - return result; + await _usedPasswordsService.AddToUsedPasswordsListAsync(user); } - public override async Task ResetPasswordAsync(User user, string token, string newPassword) + return result; + } + + public override async Task ResetPasswordAsync(User user, string token, string newPassword) + { + var result = await base.ResetPasswordAsync(user, token, newPassword); + if (result.Succeeded) { - var result = await base.ResetPasswordAsync(user, token, newPassword); - if (result.Succeeded) - { - await _usedPasswordsService.AddToUsedPasswordsListAsync(user); - } - return result; + await _usedPasswordsService.AddToUsedPasswordsListAsync(user); } - #endregion + return result; + } - #region CustomMethods + #endregion - public User FindById(int userId) - { - return _users.Find(userId); - } + #region CustomMethods + + public User FindById(int userId) + { + return _users.Find(userId); + } + + public Task FindByIdIncludeUserRolesAsync(int userId) + { + return _users.Include(x => x.Roles).FirstOrDefaultAsync(x => x.Id == userId); + } + + public Task> GetAllUsersAsync() + { + return Users.ToListAsync(); + } - public Task FindByIdIncludeUserRolesAsync(int userId) + public User GetCurrentUser() + { + if (_currentUserInScope != null) { - return _users.Include(x => x.Roles).FirstOrDefaultAsync(x => x.Id == userId); + return _currentUserInScope; } - public Task> GetAllUsersAsync() + var currentUserId = GetCurrentUserId(); + if (string.IsNullOrWhiteSpace(currentUserId)) { - return Users.ToListAsync(); + return null; } - public User GetCurrentUser() + var userId = int.Parse(currentUserId, NumberStyles.Number, CultureInfo.InvariantCulture); + return _currentUserInScope = FindById(userId); + } + + public async Task GetCurrentUserAsync() + { + if (_contextAccessor.HttpContext is null) { - if (_currentUserInScope != null) - { - return _currentUserInScope; - } + return null; + } - var currentUserId = GetCurrentUserId(); - if (string.IsNullOrWhiteSpace(currentUserId)) - { - return null; - } + return _currentUserInScope ??= await GetUserAsync(_contextAccessor.HttpContext.User); + } - var userId = int.Parse(currentUserId); - return _currentUserInScope = FindById(userId); - } + public string GetCurrentUserId() + { + return _contextAccessor.HttpContext?.User.Identity?.GetUserId(); + } - public async Task GetCurrentUserAsync() + public int? GetCurrentIntUserId() + { + var userId = _contextAccessor.HttpContext?.User.Identity?.GetUserId(); + if (string.IsNullOrEmpty(userId)) { - return _currentUserInScope ?? - (_currentUserInScope = await GetUserAsync(_contextAccessor.HttpContext.User)); + return null; } - public string GetCurrentUserId() - { - return _contextAccessor.HttpContext.User.Identity.GetUserId(); - } + return !int.TryParse(userId, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) + ? null + : result; + } - public int? CurrentUserId - { - get - { - var userId = _contextAccessor.HttpContext.User.Identity.GetUserId(); - if (string.IsNullOrEmpty(userId)) - { - return null; - } + IPasswordHasher IApplicationUserManager.PasswordHasher + { + get => base.PasswordHasher; + set => base.PasswordHasher = value; + } - return !int.TryParse(userId, out int result) ? (int?)null : result; - } - } + IList> IApplicationUserManager.UserValidators => base.UserValidators; - IPasswordHasher IApplicationUserManager.PasswordHasher { get => base.PasswordHasher; set => base.PasswordHasher = value; } + IList> IApplicationUserManager.PasswordValidators => base.PasswordValidators; - IList> IApplicationUserManager.UserValidators => base.UserValidators; + IQueryable IApplicationUserManager.Users => base.Users; - IList> IApplicationUserManager.PasswordValidators => base.PasswordValidators; + public string GetCurrentUserName() + { + return _contextAccessor.HttpContext?.User.Identity?.GetUserName(); + } - IQueryable IApplicationUserManager.Users => base.Users; + public async Task HasPasswordAsync(int userId) + { + var user = await FindByIdAsync(userId.ToString(CultureInfo.InvariantCulture)); + return user?.PasswordHash != null; + } - public string GetCurrentUserName() + public async Task HasPhoneNumberAsync(int userId) + { + var user = await FindByIdAsync(userId.ToString(CultureInfo.InvariantCulture)); + return user?.PhoneNumber != null; + } + + [SupportedOSPlatform("windows")] + public async Task GetEmailImageAsync(int? userId) + { + if (userId == null) { - return _contextAccessor.HttpContext.User.Identity.GetUserName(); + return "?".TextToImage(new TextToImageOptions()); } - public async Task HasPasswordAsync(int userId) + var user = await FindByIdAsync(userId.Value.ToString(CultureInfo.InvariantCulture)); + if (user == null) { - var user = await FindByIdAsync(userId.ToString()); - return user?.PasswordHash != null; + return "?".TextToImage(new TextToImageOptions()); } - public async Task HasPhoneNumberAsync(int userId) + if (!user.IsEmailPublic) { - var user = await FindByIdAsync(userId.ToString()); - return user?.PhoneNumber != null; + return "?".TextToImage(new TextToImageOptions()); } - public async Task GetEmailImageAsync(int? userId) - { - if (userId == null) - return "?".TextToImage(new TextToImageOptions()); + return user.Email.TextToImage(new TextToImageOptions()); + } - var user = await FindByIdAsync(userId.Value.ToString()); - if (user == null) - return "?".TextToImage(new TextToImageOptions()); + public async Task GetPagedUsersListAsync(SearchUsersViewModel model, int pageNumber) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } - if (!user.IsEmailPublic) - return "?".TextToImage(new TextToImageOptions()); + var skipRecords = pageNumber * model.MaxNumberOfRows; + var query = _users.Include(x => x.Roles).AsNoTracking(); - return user.Email.TextToImage(new TextToImageOptions()); + if (!model.ShowAllUsers) + { + query = query.Where(x => x.IsActive == model.UserIsActive); } - public async Task GetPagedUsersListAsync(SearchUsersViewModel model, int pageNumber) + if (!string.IsNullOrWhiteSpace(model.TextToFind)) { - var skipRecords = pageNumber * model.MaxNumberOfRows; - var query = _users.Include(x => x.Roles).AsNoTracking(); + model.TextToFind = model.TextToFind.ApplyCorrectYeKe(); - if (!model.ShowAllUsers) + if (model.IsPartOfEmail) { - query = query.Where(x => x.IsActive == model.UserIsActive); + query = query.Where(x => x.Email.Contains(model.TextToFind)); } - if (!string.IsNullOrWhiteSpace(model.TextToFind)) + if (model.IsUserId) { - model.TextToFind = model.TextToFind.ApplyCorrectYeKe(); - - if (model.IsPartOfEmail) - { - query = query.Where(x => x.Email.Contains(model.TextToFind)); - } - - if (model.IsUserId) - { - if (int.TryParse(model.TextToFind, out int userId)) - { - query = query.Where(x => x.Id == userId); - } - } - - if (model.IsPartOfName) - { - query = query.Where(x => x.FirstName.Contains(model.TextToFind)); - } - - if (model.IsPartOfLastName) - { - query = query.Where(x => x.LastName.Contains(model.TextToFind)); - } - - if (model.IsPartOfUserName) - { - query = query.Where(x => x.UserName.Contains(model.TextToFind)); - } - - if (model.IsPartOfLocation) + if (int.TryParse(model.TextToFind, NumberStyles.Number, CultureInfo.InvariantCulture, out var userId)) { - query = query.Where(x => x.Location.Contains(model.TextToFind)); + query = query.Where(x => x.Id == userId); } } - if (model.HasEmailConfirmed) + if (model.IsPartOfName) { - query = query.Where(x => x.EmailConfirmed); + query = query.Where(x => x.FirstName.Contains(model.TextToFind)); } - if (model.UserIsLockedOut) + if (model.IsPartOfLastName) { - query = query.Where(x => x.LockoutEnd != null); + query = query.Where(x => x.LastName.Contains(model.TextToFind)); } - if (model.HasTwoFactorEnabled) + if (model.IsPartOfUserName) { - query = query.Where(x => x.TwoFactorEnabled); + query = query.Where(x => x.UserName.Contains(model.TextToFind)); } - query = query.OrderBy(x => x.Id); - return new PagedUsersListViewModel + if (model.IsPartOfLocation) { - Paging = - { - TotalItems = await query.CountAsync() - }, - Users = await query.Skip(skipRecords).Take(model.MaxNumberOfRows).ToListAsync(), - Roles = await _roles.ToListAsync() - }; + query = query.Where(x => x.Location.Contains(model.TextToFind)); + } } - public async Task GetPagedUsersListAsync( - int pageNumber, int recordsPerPage, - string sortByField, SortOrder sortOrder, - bool showAllUsers) + if (model.HasEmailConfirmed) { - var skipRecords = pageNumber * recordsPerPage; - var query = _users.Include(x => x.Roles).AsNoTracking(); + query = query.Where(x => x.EmailConfirmed); + } - if (!showAllUsers) - { - query = query.Where(x => x.IsActive); - } + if (model.UserIsLockedOut) + { + query = query.Where(x => x.LockoutEnd != null); + } - switch (sortByField) - { - case nameof(User.Id): - query = sortOrder == SortOrder.Descending ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id); - break; - default: - query = sortOrder == SortOrder.Descending ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id); - break; - } + if (model.HasTwoFactorEnabled) + { + query = query.Where(x => x.TwoFactorEnabled); + } - return new PagedUsersListViewModel + query = query.OrderBy(x => x.Id); + return new PagedUsersListViewModel + { + Paging = { - Paging = - { - TotalItems = await query.CountAsync() - }, - Users = await query.Skip(skipRecords).Take(recordsPerPage).ToListAsync(), - Roles = await _roles.ToListAsync() - }; + TotalItems = await query.CountAsync() + }, + Users = await query.Skip(skipRecords).Take(model.MaxNumberOfRows).ToListAsync(), + Roles = await _roles.ToListAsync() + }; + } + + public async Task GetPagedUsersListAsync( + int pageNumber, int recordsPerPage, + string sortByField, SortOrder sortOrder, + bool showAllUsers) + { + var skipRecords = pageNumber * recordsPerPage; + var query = _users.Include(x => x.Roles).AsNoTracking(); + + if (!showAllUsers) + { + query = query.Where(x => x.IsActive); } - public async Task UpdateUserAndSecurityStampAsync(int userId, Action action) + switch (sortByField) { - var user = await FindByIdIncludeUserRolesAsync(userId); - if (user == null) - { - return IdentityResult.Failed(new IdentityError - { - Code = "UserNotFound", - Description = "کاربر مورد نظر یافت نشد." - }); - } + default: + query = sortOrder == SortOrder.Descending + ? query.OrderByDescending(x => x.Id) + : query.OrderBy(x => x.Id); + break; + } - action(user); + return new PagedUsersListViewModel + { + Paging = + { + TotalItems = await query.CountAsync() + }, + Users = await query.Skip(skipRecords).Take(recordsPerPage).ToListAsync(), + Roles = await _roles.ToListAsync() + }; + } - var result = await UpdateAsync(user); - if (!result.Succeeded) + public async Task UpdateUserAndSecurityStampAsync(int userId, Action action) + { + var user = await FindByIdIncludeUserRolesAsync(userId); + if (user == null) + { + return IdentityResult.Failed(new IdentityError { - return result; - } - return await UpdateSecurityStampAsync(user); + Code = "UserNotFound", + Description = "کاربر مورد نظر یافت نشد." + }); } - public async Task AddOrUpdateUserRolesAsync(int userId, IList selectedRoleIds, Action action = null) + action?.Invoke(user); + + var result = await UpdateAsync(user); + if (!result.Succeeded) { - var user = await FindByIdIncludeUserRolesAsync(userId); - if (user == null) - { - return IdentityResult.Failed(new IdentityError - { - Code = "UserNotFound", - Description = "کاربر مورد نظر یافت نشد." - }); - } + return result; + } - var currentUserRoleIds = user.Roles.Select(x => x.RoleId).ToList(); + return await UpdateSecurityStampAsync(user); + } - if (selectedRoleIds == null) + public async Task AddOrUpdateUserRolesAsync(int userId, IList selectedRoleIds, + Action action = null) + { + var user = await FindByIdIncludeUserRolesAsync(userId); + if (user == null) + { + return IdentityResult.Failed(new IdentityError { - selectedRoleIds = new List(); - } + Code = "UserNotFound", + Description = "کاربر مورد نظر یافت نشد." + }); + } - var newRolesToAdd = selectedRoleIds.Except(currentUserRoleIds).ToList(); - foreach (var roleId in newRolesToAdd) - { - user.Roles.Add(new UserRole { RoleId = roleId, UserId = user.Id }); - } + var currentUserRoleIds = user.Roles.Select(x => x.RoleId).ToList(); - var removedRoles = currentUserRoleIds.Except(selectedRoleIds).ToList(); - foreach (var roleId in removedRoles) - { - var userRole = user.Roles.SingleOrDefault(ur => ur.RoleId == roleId); - if (userRole != null) - { - user.Roles.Remove(userRole); - } - } + selectedRoleIds ??= new List(); - action?.Invoke(user); + var newRolesToAdd = selectedRoleIds.Except(currentUserRoleIds).ToList(); + foreach (var roleId in newRolesToAdd) + { + user.Roles.Add(new UserRole { RoleId = roleId, UserId = user.Id }); + } - var result = await UpdateAsync(user); - if (!result.Succeeded) + var removedRoles = currentUserRoleIds.Except(selectedRoleIds).ToList(); + foreach (var roleId in removedRoles) + { + var userRole = user.Roles.SingleOrDefault(ur => ur.RoleId == roleId); + if (userRole != null) { - return result; + user.Roles.Remove(userRole); } - return await UpdateSecurityStampAsync(user); } - Task IApplicationUserManager.UpdatePasswordHash(User user, string newPassword, bool validatePassword) + action?.Invoke(user); + + var result = await UpdateAsync(user); + if (!result.Succeeded) { - return base.UpdatePasswordHash(user, newPassword, validatePassword); + return result; } - #endregion + return await UpdateSecurityStampAsync(user); } + + Task IApplicationUserManager.UpdatePasswordHash(User user, string newPassword, + bool validatePassword) + { + return base.UpdatePasswordHash(user, newPassword, validatePassword); + } + + #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserStore.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserStore.cs index ad7a704..50a176a 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ApplicationUserStore.cs @@ -1,120 +1,149 @@ -using System; -using System.Security.Claims; -using System.Threading; -using System.Threading.Tasks; +using System.Security.Claims; using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2578 +/// +public class ApplicationUserStore : + UserStore, + IApplicationUserStore { - /// - /// More info: http://www.dotnettips.info/post/2578 - /// - public class ApplicationUserStore : - UserStore, - IApplicationUserStore + private readonly IdentityErrorDescriber _describer; + private readonly IUnitOfWork _uow; + + public ApplicationUserStore( + IUnitOfWork uow, + IdentityErrorDescriber describer) + : base((ApplicationDbContext)uow, describer) { - private readonly IUnitOfWork _uow; - private readonly IdentityErrorDescriber _describer; + _uow = uow ?? throw new ArgumentNullException(nameof(uow)); + _describer = describer ?? throw new ArgumentNullException(nameof(describer)); + } - public ApplicationUserStore( - IUnitOfWork uow, - IdentityErrorDescriber describer) - : base((ApplicationDbContext)uow, describer) + #region BaseClass + + protected override UserClaim CreateUserClaim(User user, Claim claim) + { + if (user == null) { - _uow = uow ?? throw new ArgumentNullException(nameof(_uow)); - _describer = describer ?? throw new ArgumentNullException(nameof(_describer)); + throw new ArgumentNullException(nameof(user)); } - #region BaseClass + var userClaim = new UserClaim { UserId = user.Id }; + userClaim.InitializeFromClaim(claim); + return userClaim; + } - protected override UserClaim CreateUserClaim(User user, Claim claim) + protected override UserLogin CreateUserLogin(User user, UserLoginInfo login) + { + if (user == null) { - var userClaim = new UserClaim { UserId = user.Id }; - userClaim.InitializeFromClaim(claim); - return userClaim; + throw new ArgumentNullException(nameof(user)); } - protected override UserLogin CreateUserLogin(User user, UserLoginInfo login) + if (login == null) { - return new UserLogin - { - UserId = user.Id, - ProviderKey = login.ProviderKey, - LoginProvider = login.LoginProvider, - ProviderDisplayName = login.ProviderDisplayName - }; + throw new ArgumentNullException(nameof(login)); } - protected override UserRole CreateUserRole(User user, Role role) + return new UserLogin { - return new UserRole - { - UserId = user.Id, - RoleId = role.Id - }; - } + UserId = user.Id, + ProviderKey = login.ProviderKey, + LoginProvider = login.LoginProvider, + ProviderDisplayName = login.ProviderDisplayName + }; + } - protected override UserToken CreateUserToken(User user, string loginProvider, string name, string value) + protected override UserRole CreateUserRole(User user, Role role) + { + if (user == null) { - return new UserToken - { - UserId = user.Id, - LoginProvider = loginProvider, - Name = name, - Value = value - }; + throw new ArgumentNullException(nameof(user)); } - Task IApplicationUserStore.AddUserTokenAsync(UserToken token) + if (role == null) { - return base.AddUserTokenAsync(token); + throw new ArgumentNullException(nameof(role)); } - Task IApplicationUserStore.FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken) + return new UserRole { - return base.FindRoleAsync(normalizedRoleName, cancellationToken); - } + UserId = user.Id, + RoleId = role.Id + }; + } - Task IApplicationUserStore.FindTokenAsync(User user, string loginProvider, string name, CancellationToken cancellationToken) + protected override UserToken CreateUserToken(User user, string loginProvider, string name, string value) + { + if (user == null) { - return base.FindTokenAsync(user, loginProvider, name, cancellationToken); + throw new ArgumentNullException(nameof(user)); } - Task IApplicationUserStore.FindUserAsync(int userId, CancellationToken cancellationToken) + return new UserToken { - return base.FindUserAsync(userId, cancellationToken); - } + UserId = user.Id, + LoginProvider = loginProvider, + Name = name, + Value = value + }; + } - Task IApplicationUserStore.FindUserLoginAsync(int userId, string loginProvider, string providerKey, CancellationToken cancellationToken) - { - return base.FindUserLoginAsync(userId, loginProvider, providerKey, cancellationToken); - } + Task IApplicationUserStore.AddUserTokenAsync(UserToken token) + { + return base.AddUserTokenAsync(token); + } - Task IApplicationUserStore.FindUserLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) - { - return base.FindUserLoginAsync(loginProvider, providerKey, cancellationToken); - } + Task IApplicationUserStore.FindRoleAsync(string normalizedRoleName, CancellationToken cancellationToken) + { + return base.FindRoleAsync(normalizedRoleName, cancellationToken); + } - Task IApplicationUserStore.FindUserRoleAsync(int userId, int roleId, CancellationToken cancellationToken) - { - return base.FindUserRoleAsync(userId, roleId, cancellationToken); - } + Task IApplicationUserStore.FindTokenAsync(User user, string loginProvider, string name, + CancellationToken cancellationToken) + { + return base.FindTokenAsync(user, loginProvider, name, cancellationToken); + } - Task IApplicationUserStore.RemoveUserTokenAsync(UserToken token) - { - return base.RemoveUserTokenAsync(token); - } + Task IApplicationUserStore.FindUserAsync(int userId, CancellationToken cancellationToken) + { + return base.FindUserAsync(userId, cancellationToken); + } - #endregion + Task IApplicationUserStore.FindUserLoginAsync(int userId, string loginProvider, string providerKey, + CancellationToken cancellationToken) + { + return base.FindUserLoginAsync(userId, loginProvider, providerKey, cancellationToken); + } - #region CustomMethods + Task IApplicationUserStore.FindUserLoginAsync(string loginProvider, string providerKey, + CancellationToken cancellationToken) + { + return base.FindUserLoginAsync(loginProvider, providerKey, cancellationToken); + } - // Add custom methods here + Task IApplicationUserStore.FindUserRoleAsync(int userId, int roleId, CancellationToken cancellationToken) + { + return base.FindUserRoleAsync(userId, roleId, cancellationToken); + } - #endregion + Task IApplicationUserStore.RemoveUserTokenAsync(UserToken token) + { + return base.RemoveUserTokenAsync(token); } + + #endregion + + #region CustomMethods + + // Add custom methods here + + #endregion } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/AuthMessageSender.cs b/src/ASPNETCoreIdentitySample.Services/Identity/AuthMessageSender.cs index 94a2ac9..cdfb55d 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/AuthMessageSender.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/AuthMessageSender.cs @@ -1,54 +1,51 @@ using ASPNETCoreIdentitySample.Services.Contracts.Identity; -using Microsoft.Extensions.Options; -using System.Threading.Tasks; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using DNTCommon.Web.Core; -using System; +using Microsoft.Extensions.Options; + +namespace ASPNETCoreIdentitySample.Services.Identity; -namespace ASPNETCoreIdentitySample.Services.Identity +/// +/// More info: http://www.dntips.ir/post/2551 +/// And http://www.dntips.ir/post/2564 +/// +public class AuthMessageSender : IEmailSender, ISmsSender { - /// - /// More info: http://www.dotnettips.info/post/2551 - /// And http://www.dotnettips.info/post/2564 - /// - public class AuthMessageSender : IEmailSender, ISmsSender - { - private readonly IOptionsSnapshot _smtpConfig; - private readonly IWebMailService _webMailService; + private readonly IOptionsSnapshot _smtpConfig; + private readonly IWebMailService _webMailService; - public AuthMessageSender( - IOptionsSnapshot smtpConfig, - IWebMailService webMailService) - { - _smtpConfig = smtpConfig ?? throw new ArgumentNullException(nameof(_smtpConfig)); - _webMailService = webMailService ?? throw new ArgumentNullException(nameof(webMailService)); - } + public AuthMessageSender( + IOptionsSnapshot smtpConfig, + IWebMailService webMailService) + { + _smtpConfig = smtpConfig ?? throw new ArgumentNullException(nameof(smtpConfig)); + _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 - ); - } + 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 + ); + } - public Task SendEmailAsync(string email, string subject, string message) - { - return _webMailService.SendEmailAsync( - _smtpConfig.Value.Smtp, - new[] { new MailAddress { ToName = "", ToAddress = email } }, - subject, - message - ); - } + public Task SendEmailAsync(string email, string subject, string message) + { + return _webMailService.SendEmailAsync( + _smtpConfig.Value.Smtp, + new[] { 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); - } + public Task SendSmsAsync(string number, string message) + { + // Plug in your SMS service here to send a text message. + return Task.FromResult(0); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectionTokenProviderOptions.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectionTokenProviderOptions.cs new file mode 100644 index 0000000..ea0d6fa --- /dev/null +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectionTokenProviderOptions.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Identity; + +namespace ASPNETCoreIdentitySample.Services.Identity; + +public class ConfirmEmailDataProtectionTokenProviderOptions : DataProtectionTokenProviderOptions +{ +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectorTokenProvider.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectorTokenProvider.cs index bd3f41e..9d0492d 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectorTokenProvider.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ConfirmEmailDataProtectorTokenProvider.cs @@ -3,24 +3,20 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace ASPNETCoreIdentitySample.Services.Identity -{ - public class ConfirmEmailDataProtectionTokenProviderOptions : DataProtectionTokenProviderOptions - { } +namespace ASPNETCoreIdentitySample.Services.Identity; - /// - /// How to override the default (1 day) TokenLifeSpan for the email confirmations. - /// - public class ConfirmEmailDataProtectorTokenProvider : DataProtectorTokenProvider where TUser : class +/// +/// How to override the default (1 day) TokenLifeSpan for the email confirmations. +/// +public class ConfirmEmailDataProtectorTokenProvider : DataProtectorTokenProvider where TUser : class +{ + public ConfirmEmailDataProtectorTokenProvider( + IDataProtectionProvider dataProtectionProvider, + IOptions options, + ILogger> logger) + : base(dataProtectionProvider, options, logger) { - 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/ConstantPolicies.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ConstantPolicies.cs index 061de0a..5c48137 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ConstantPolicies.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ConstantPolicies.cs @@ -1,8 +1,7 @@ -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +public static class ConstantPolicies { - public static class ConstantPolicies - { - public const string DynamicPermission = nameof(DynamicPermission); - public const string DynamicPermissionClaimType = nameof(DynamicPermission); - } + public const string DynamicPermission = nameof(DynamicPermission); + public const string DynamicPermissionClaimType = nameof(DynamicPermission); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/ConstantRoles.cs b/src/ASPNETCoreIdentitySample.Services/Identity/ConstantRoles.cs index 702cb86..53a3b55 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/ConstantRoles.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/ConstantRoles.cs @@ -1,7 +1,6 @@ -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +public static class ConstantRoles { - public static class ConstantRoles - { - public const string Admin = nameof(Admin); - } + public const string Admin = nameof(Admin); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/CustomIdentityErrorDescriber.cs b/src/ASPNETCoreIdentitySample.Services/Identity/CustomIdentityErrorDescriber.cs index 194e3d1..abd2e69 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/CustomIdentityErrorDescriber.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/CustomIdentityErrorDescriber.cs @@ -1,208 +1,210 @@ using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2582 +/// +public class CustomIdentityErrorDescriber : IdentityErrorDescriber { - /// - /// More info: http://www.dotnettips.info/post/2582 - /// - public class CustomIdentityErrorDescriber : IdentityErrorDescriber - { - public override IdentityError ConcurrencyFailure() - { - return new IdentityError - { - Code = nameof(ConcurrencyFailure), - Description = "رکورد جاری پیشتر ویرایش شده‌است و تغییرات شما آن‌را بازنویسی خواهد کرد." - }; - } - - public override IdentityError DefaultError() - { - return new IdentityError - { - Code = nameof(DefaultError), - Description = "خطایی رخ داده‌است." - }; - } - - public override IdentityError DuplicateEmail(string email) - { - return new IdentityError - { - Code = nameof(DuplicateEmail), - Description = string.Format("ایمیل '{0}' هم اکنون مورد استفاده است.", email) - }; - } - - public override IdentityError DuplicateRoleName(string role) - { - return new IdentityError - { - Code = nameof(DuplicateRoleName), - Description = string.Format("نقش '{0}' هم اکنون مورد استفاده‌است.", role) - }; - } - - public override IdentityError DuplicateUserName(string userName) - { - return new IdentityError - { - Code = nameof(DuplicateUserName), - Description = string.Format("نام کاربری '{0}' هم اکنون مورد استفاده‌است.", userName) - }; - } - - public override IdentityError InvalidEmail(string email) - { - return new IdentityError - { - Code = nameof(InvalidEmail), - Description = string.Format("ایمیل '{0}' معتبر نیست.", email) - }; - } - - public override IdentityError InvalidRoleName(string role) - { - return new IdentityError - { - Code = nameof(InvalidRoleName), - Description = string.Format("نقش '{0}' معتبر نیست.", role) - }; - } - - public override IdentityError InvalidToken() - { - return new IdentityError - { - Code = nameof(InvalidToken), - Description = "توکن غیر معتبر." - }; - } - - public override IdentityError InvalidUserName(string userName) - { - return new IdentityError - { - Code = nameof(InvalidUserName), - Description = string.Format("نام کاربری '{0}' معتبر نیست و تنها می‌تواند حاوی حروف و یا ارقام باشد.", userName) - }; - } - - public override IdentityError LoginAlreadyAssociated() - { - return new IdentityError - { - Code = nameof(LoginAlreadyAssociated), - Description = "این کاربر پیشتر اضافه شده‌است." - }; - } - - public override IdentityError PasswordMismatch() - { - return new IdentityError - { - Code = nameof(PasswordMismatch), - Description = "کلمه‌ی عبور نامعتبر." - }; - } - - public override IdentityError PasswordRequiresDigit() - { - return new IdentityError - { - Code = nameof(PasswordRequiresDigit), - Description = "کلمه‌ی عبور باید حداقل دارای یک رقم بین 0 تا 9 باشد." - }; - } - - public override IdentityError PasswordRequiresLower() - { - return new IdentityError - { - Code = nameof(PasswordRequiresLower), - Description = "کلمه‌ی عبور باید حداقل دارای یک حرف کوچک انگلیسی باشد." - }; - } - - public override IdentityError PasswordRequiresNonAlphanumeric() - { - return new IdentityError - { - Code = nameof(PasswordRequiresNonAlphanumeric), - Description = "کلمه‌ی عبور باید حداقل دارای یک حرف خارج از حروف الفبای انگلیسی و همچنین اعداد باشد." - }; - } - - public override IdentityError PasswordRequiresUniqueChars(int uniqueChars) - { - return new IdentityError - { - Code = nameof(PasswordRequiresUniqueChars), - Description = "کلمه‌ی عبور باید حداقل داراى {0} حرف متفاوت باشد." - }; - } - - public override IdentityError PasswordRequiresUpper() - { - return new IdentityError - { - Code = nameof(PasswordRequiresUpper), - Description = "کلمه‌ی عبور باید حداقل دارای یک حرف بزرگ انگلیسی باشد." - }; - } - - public override IdentityError PasswordTooShort(int length) - { - return new IdentityError - { - Code = nameof(PasswordTooShort), - Description = string.Format("کلمه‌ی عبور باید حداقل {0} حرف باشد.", length) - }; - } - - public override IdentityError RecoveryCodeRedemptionFailed() - { - return new IdentityError - { - Code = nameof(RecoveryCodeRedemptionFailed), - Description = "بازیابى با شکست مواجه شد." - }; - } - - public override IdentityError UserAlreadyHasPassword() - { - return new IdentityError - { - Code = nameof(UserAlreadyHasPassword), - Description = "کلمه‌ی عبور کاربر پیشتر تنظیم شده‌است." - }; - } - - public override IdentityError UserAlreadyInRole(string role) - { - return new IdentityError - { - Code = nameof(UserAlreadyInRole), - Description = string.Format("کاربر هم اکنون دارای نقش '{0}' است.", role) - }; - } - - public override IdentityError UserLockoutNotEnabled() - { - return new IdentityError - { - Code = nameof(UserLockoutNotEnabled), - Description = "قفل شدن اکانت برای این کاربر تنظیم نشده‌است." - }; - } - - public override IdentityError UserNotInRole(string role) - { - return new IdentityError - { - Code = nameof(UserNotInRole), - Description = "کاربر دارای نقش '{0}' نیست." - }; - } + public override IdentityError ConcurrencyFailure() + { + return new IdentityError + { + Code = nameof(ConcurrencyFailure), + Description = "رکورد جاری پیشتر ویرایش شده‌است و تغییرات شما آن‌را بازنویسی خواهد کرد." + }; + } + + public override IdentityError DefaultError() + { + return new IdentityError + { + Code = nameof(DefaultError), + Description = "خطایی رخ داده‌است." + }; + } + + public override IdentityError DuplicateEmail(string email) + { + return new IdentityError + { + Code = nameof(DuplicateEmail), + Description = string.Format(CultureInfo.InvariantCulture, "ایمیل '{0}' هم اکنون مورد استفاده است.", email) + }; + } + + public override IdentityError DuplicateRoleName(string role) + { + return new IdentityError + { + Code = nameof(DuplicateRoleName), + Description = string.Format(CultureInfo.InvariantCulture, "نقش '{0}' هم اکنون مورد استفاده‌است.", role) + }; + } + + public override IdentityError DuplicateUserName(string userName) + { + return new IdentityError + { + Code = nameof(DuplicateUserName), + Description = string.Format(CultureInfo.InvariantCulture, "نام کاربری '{0}' هم اکنون مورد استفاده‌است.", + userName) + }; + } + + public override IdentityError InvalidEmail(string email) + { + return new IdentityError + { + Code = nameof(InvalidEmail), + Description = string.Format(CultureInfo.InvariantCulture, "ایمیل '{0}' معتبر نیست.", email) + }; + } + + public override IdentityError InvalidRoleName(string role) + { + return new IdentityError + { + Code = nameof(InvalidRoleName), + Description = string.Format(CultureInfo.InvariantCulture, "نقش '{0}' معتبر نیست.", role) + }; + } + + public override IdentityError InvalidToken() + { + return new IdentityError + { + Code = nameof(InvalidToken), + Description = "توکن غیر معتبر." + }; + } + + public override IdentityError InvalidUserName(string userName) + { + return new IdentityError + { + Code = nameof(InvalidUserName), + Description = string.Format(CultureInfo.InvariantCulture, + "نام کاربری '{0}' معتبر نیست و تنها می‌تواند حاوی حروف و یا ارقام باشد.", + userName) + }; + } + + public override IdentityError LoginAlreadyAssociated() + { + return new IdentityError + { + Code = nameof(LoginAlreadyAssociated), + Description = "این کاربر پیشتر اضافه شده‌است." + }; + } + + public override IdentityError PasswordMismatch() + { + return new IdentityError + { + Code = nameof(PasswordMismatch), + Description = "کلمه‌ی عبور نامعتبر." + }; + } + + public override IdentityError PasswordRequiresDigit() + { + return new IdentityError + { + Code = nameof(PasswordRequiresDigit), + Description = "کلمه‌ی عبور باید حداقل دارای یک رقم بین 0 تا 9 باشد." + }; + } + + public override IdentityError PasswordRequiresLower() + { + return new IdentityError + { + Code = nameof(PasswordRequiresLower), + Description = "کلمه‌ی عبور باید حداقل دارای یک حرف کوچک انگلیسی باشد." + }; + } + + public override IdentityError PasswordRequiresNonAlphanumeric() + { + return new IdentityError + { + Code = nameof(PasswordRequiresNonAlphanumeric), + Description = "کلمه‌ی عبور باید حداقل دارای یک حرف خارج از حروف الفبای انگلیسی و همچنین اعداد باشد." + }; + } + + public override IdentityError PasswordRequiresUniqueChars(int uniqueChars) + { + return new IdentityError + { + Code = nameof(PasswordRequiresUniqueChars), + Description = "کلمه‌ی عبور باید حداقل داراى {0} حرف متفاوت باشد." + }; + } + + public override IdentityError PasswordRequiresUpper() + { + return new IdentityError + { + Code = nameof(PasswordRequiresUpper), + Description = "کلمه‌ی عبور باید حداقل دارای یک حرف بزرگ انگلیسی باشد." + }; + } + + public override IdentityError PasswordTooShort(int length) + { + return new IdentityError + { + Code = nameof(PasswordTooShort), + Description = string.Format(CultureInfo.InvariantCulture, "کلمه‌ی عبور باید حداقل {0} حرف باشد.", length) + }; + } + + public override IdentityError RecoveryCodeRedemptionFailed() + { + return new IdentityError + { + Code = nameof(RecoveryCodeRedemptionFailed), + Description = "بازیابى با شکست مواجه شد." + }; + } + + public override IdentityError UserAlreadyHasPassword() + { + return new IdentityError + { + Code = nameof(UserAlreadyHasPassword), + Description = "کلمه‌ی عبور کاربر پیشتر تنظیم شده‌است." + }; + } + + public override IdentityError UserAlreadyInRole(string role) + { + return new IdentityError + { + Code = nameof(UserAlreadyInRole), + Description = string.Format(CultureInfo.InvariantCulture, "کاربر هم اکنون دارای نقش '{0}' است.", role) + }; + } + + public override IdentityError UserLockoutNotEnabled() + { + return new IdentityError + { + Code = nameof(UserLockoutNotEnabled), + Description = "قفل شدن اکانت برای این کاربر تنظیم نشده‌است." + }; + } + + public override IdentityError UserNotInRole(string role) + { + return new IdentityError + { + Code = nameof(UserNotInRole), + Description = "کاربر دارای نقش '{0}' نیست." + }; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/CustomNormalizer.cs b/src/ASPNETCoreIdentitySample.Services/Identity/CustomNormalizer.cs index 5854ceb..63c0228 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/CustomNormalizer.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/CustomNormalizer.cs @@ -1,69 +1,69 @@ -using System; -using System.Linq; -using Microsoft.AspNetCore.Identity; -using ASPNETCoreIdentitySample.Common.PersianToolkit; +using ASPNETCoreIdentitySample.Common.PersianToolkit; using DNTPersianUtils.Core; +using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2579 +/// +public class CustomNormalizer : ILookupNormalizer { - /// - /// More info: http://www.dotnettips.info/post/2579 - /// - public class CustomNormalizer : ILookupNormalizer + public string NormalizeEmail(string email) { - public string NormalizeEmail(string email) + if (string.IsNullOrWhiteSpace(email)) { - if (string.IsNullOrWhiteSpace(email)) - { - return null; - } - - email = email.Trim(); - email = fixGmailDots(email); - email = email.ToUpperInvariant(); - return email; + return null; } - public string NormalizeName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - return null; - } + email = email.Trim(); + email = FixGmailDots(email); + email = email.ToUpperInvariant(); + return email; + } - name = name.Trim(); - name = name.ApplyCorrectYeKe() - .RemoveDiacritics() - .CleanUnderLines() - .RemovePunctuation(); - name = name.Trim().Replace(" ", ""); - name = name.ToUpperInvariant(); - return name; + public string NormalizeName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + return null; } - private static string fixGmailDots(string email) - { - email = email.ToLowerInvariant().Trim(); - var emailParts = email.Split('@'); - var name = emailParts[0].Replace(".", string.Empty); + name = name.Trim(); + name = name.ApplyCorrectYeKe() + .RemoveDiacritics() + .CleanUnderLines() + .RemovePunctuation(); + name = name.Trim().Replace(" ", "", 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 plusIndex = name.IndexOf("+", StringComparison.OrdinalIgnoreCase); - if (plusIndex != -1) - { - name = name.Substring(0, plusIndex); - } + var plusIndex = name.IndexOf("+", StringComparison.OrdinalIgnoreCase); + if (plusIndex != -1) + { + name = name.Substring(0, plusIndex); + } - var emailDomain = emailParts[1]; - emailDomain = emailDomain.Replace("googlemail.com", "gmail.com"); + var emailDomain = emailParts[1]; + emailDomain = emailDomain.Replace("googlemail.com", "gmail.com", StringComparison.OrdinalIgnoreCase); - string[] domainsAllowedDots = - { - "gmail.com", - "facebook.com" - }; + string[] domainsAllowedDots = + { + "gmail.com", + "facebook.com" + }; - var isFromDomainsAllowedDots = domainsAllowedDots.Any(domain => emailDomain.Equals(domain)); - return !isFromDomainsAllowedDots ? email : string.Format("{0}@{1}", name, emailDomain); - } + var isFromDomainsAllowedDots = + domainsAllowedDots.Any(domain => emailDomain.Equals(domain, StringComparison.OrdinalIgnoreCase)); + return !isFromDomainsAllowedDots + ? email + : string.Format(CultureInfo.InvariantCulture, "{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 357ce2b..ef064d1 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/CustomPasswordValidator.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/CustomPasswordValidator.cs @@ -1,119 +1,143 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// Extending the Built-in Password Validation +/// More info: http://www.dntips.ir/post/2579 +/// +public class CustomPasswordValidator : PasswordValidator { - /// - /// Extending the Built-in Password Validation - /// More info: http://www.dotnettips.info/post/2579 - /// - public class CustomPasswordValidator : PasswordValidator + private readonly ISet _passwordsBanList; + private readonly IUsedPasswordsService _usedPasswordsService; + + public CustomPasswordValidator( + IdentityErrorDescriber errors, // How to use CustomIdentityErrorDescriber + IOptionsSnapshot configurationRoot, + IUsedPasswordsService usedPasswordsService) : base(errors) { - private readonly IUsedPasswordsService _usedPasswordsService; - private readonly ISet _passwordsBanList; + _usedPasswordsService = usedPasswordsService ?? throw new ArgumentNullException(nameof(usedPasswordsService)); + if (configurationRoot == null) + { + throw new ArgumentNullException(nameof(configurationRoot)); + } + + _passwordsBanList = + new HashSet(configurationRoot.Value.PasswordsBanList, StringComparer.OrdinalIgnoreCase); - public CustomPasswordValidator( - IdentityErrorDescriber errors,// How to use CustomIdentityErrorDescriber - IOptionsSnapshot configurationRoot, - IUsedPasswordsService usedPasswordsService) : base(errors) + if (!_passwordsBanList.Any()) { - _usedPasswordsService = usedPasswordsService ?? throw new ArgumentNullException(nameof(usedPasswordsService)); - if (configurationRoot == null) throw new ArgumentNullException(nameof(configurationRoot)); - _passwordsBanList = new HashSet(configurationRoot.Value.PasswordsBanList, StringComparer.OrdinalIgnoreCase); + throw new InvalidOperationException("Please fill the passwords ban list in the appsettings.json file."); + } + } + + public override async Task ValidateAsync(UserManager manager, User user, string password) + { + var errors = new List(); - if (!_passwordsBanList.Any()) + if (string.IsNullOrWhiteSpace(password)) + { + errors.Add(new IdentityError { - throw new InvalidOperationException("Please fill the passwords ban list in the appsettings.json file."); - } + Code = "PasswordIsNotSet", + Description = "لطفا کلمه‌ی عبور را تکمیل کنید." + }); + return IdentityResult.Failed(errors.ToArray()); } - public override async Task ValidateAsync(UserManager manager, User user, string password) + if (string.IsNullOrWhiteSpace(user?.UserName)) { - var errors = new List(); - - if (string.IsNullOrWhiteSpace(password)) - { - errors.Add(new IdentityError - { - Code = "PasswordIsNotSet", - Description = "لطفا کلمه‌ی عبور را تکمیل کنید." - }); - return IdentityResult.Failed(errors.ToArray()); - } - - if (string.IsNullOrWhiteSpace(user?.UserName)) + errors.Add(new IdentityError { - errors.Add(new IdentityError - { - Code = "UserNameIsNotSet", - Description = "لطفا نام کاربری را تکمیل کنید." - }); - return IdentityResult.Failed(errors.ToArray()); - } - - // First use the built-in validator - var result = await base.ValidateAsync(manager, user, password); - errors = result.Succeeded ? new List() : result.Errors.ToList(); - - // Extending the built-in validator - if (password.Contains(user.UserName, StringComparison.OrdinalIgnoreCase)) + Code = "UserNameIsNotSet", + Description = "لطفا نام کاربری را تکمیل کنید." + }); + return IdentityResult.Failed(errors.ToArray()); + } + + // First use the built-in validator + var result = await base.ValidateAsync(manager, user, password); + errors = result.Succeeded ? new List() : result.Errors.ToList(); + + // Extending the built-in validator + if (password.Contains(user.UserName, StringComparison.OrdinalIgnoreCase)) + { + errors.Add(new IdentityError { - errors.Add(new IdentityError - { - Code = "PasswordContainsUserName", - Description = "کلمه‌ی عبور نمی‌تواند حاوی قسمتی از نام کاربری باشد." - }); - return IdentityResult.Failed(errors.ToArray()); - } - - if (!isSafePasword(password)) + Code = "PasswordContainsUserName", + Description = "کلمه‌ی عبور نمی‌تواند حاوی قسمتی از نام کاربری باشد." + }); + return IdentityResult.Failed(errors.ToArray()); + } + + if (!IsSafePasword(password)) + { + errors.Add(new IdentityError { - errors.Add(new IdentityError - { - Code = "PasswordIsNotSafe", - Description = "کلمه‌ی عبور وارد شده به سادگی قابل حدس زدن است." - }); - return IdentityResult.Failed(errors.ToArray()); - } - - if (await _usedPasswordsService.IsPreviouslyUsedPasswordAsync(user, password)) + Code = "PasswordIsNotSafe", + Description = "کلمه‌ی عبور وارد شده به سادگی قابل حدس زدن است." + }); + return IdentityResult.Failed(errors.ToArray()); + } + + if (await _usedPasswordsService.IsPreviouslyUsedPasswordAsync(user, password)) + { + errors.Add(new IdentityError { - errors.Add(new IdentityError - { - Code = "IsPreviouslyUsedPassword", - Description = "لطفا کلمه‌ی عبور دیگری را انتخاب کنید. این کلمه‌ی عبور پیشتر توسط شما استفاده شده‌است و تکراری می‌باشد." - }); - return IdentityResult.Failed(errors.ToArray()); - } - - return !errors.Any() ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray()); + Code = "IsPreviouslyUsedPassword", + Description = + "لطفا کلمه‌ی عبور دیگری را انتخاب کنید. این کلمه‌ی عبور پیشتر توسط شما استفاده شده‌است و تکراری می‌باشد." + }); + return IdentityResult.Failed(errors.ToArray()); + } + + return !errors.Any() ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray()); + } + + private static bool AreAllCharsEqual(string data) + { + if (string.IsNullOrWhiteSpace(data)) + { + return false; } - private static bool areAllCharsEqual(string data) + data = data.ToLowerInvariant(); + var firstElement = data.ElementAt(0); + var euqalCharsLen = data.ToCharArray().Count(x => x == firstElement); + if (euqalCharsLen == data.Length) + { + return true; + } + + return false; + } + + private bool IsSafePasword(string data) + { + if (string.IsNullOrWhiteSpace(data)) { - if (string.IsNullOrWhiteSpace(data)) return false; - data = data.ToLowerInvariant(); - var firstElement = data.ElementAt(0); - var euqalCharsLen = data.ToCharArray().Count(x => x == firstElement); - if (euqalCharsLen == data.Length) return true; return false; } - private bool isSafePasword(string data) + if (data.Length < 5) { - if (string.IsNullOrWhiteSpace(data)) return false; - if (data.Length < 5) return false; - if (_passwordsBanList.Contains(data.ToLowerInvariant())) return false; - if (areAllCharsEqual(data)) return false; + return false; + } - return true; + if (_passwordsBanList.Contains(data.ToLowerInvariant())) + { + return false; } + + if (AreAllCharsEqual(data)) + { + return false; + } + + return true; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/CustomSecurityStampValidator.cs b/src/ASPNETCoreIdentitySample.Services/Identity/CustomSecurityStampValidator.cs index 3726590..8848a7c 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/CustomSecurityStampValidator.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/CustomSecurityStampValidator.cs @@ -1,67 +1,70 @@ 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.Options; -using System.Threading.Tasks; -using System; -using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace ASPNETCoreIdentitySample.Services.Identity; -namespace ASPNETCoreIdentitySample.Services.Identity +/// +/// Keep track of on-line users +/// +public class CustomSecurityStampValidator : SecurityStampValidator { - /// - /// Keep track of on-line users - /// - public class CustomSecurityStampValidator : SecurityStampValidator + private readonly ISystemClock _clock; + private readonly IOptions _options; + private readonly IApplicationSignInManager _signInManager; + private readonly ISiteStatService _siteStatService; + + public CustomSecurityStampValidator( + IOptions options, + IApplicationSignInManager signInManager, + ISystemClock clock, + ISiteStatService siteStatService, + ILoggerFactory logger) + : base(options, (SignInManager)signInManager, clock, logger) { - private readonly IOptions _options; - private readonly IApplicationSignInManager _signInManager; - private readonly ISiteStatService _siteStatService; - private readonly ISystemClock _clock; + _options = options ?? throw new ArgumentNullException(nameof(options)); + _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); + _siteStatService = siteStatService ?? throw new ArgumentNullException(nameof(siteStatService)); + _clock = clock; + } - public CustomSecurityStampValidator( - IOptions options, - IApplicationSignInManager signInManager, - ISystemClock clock, - ISiteStatService siteStatService, - ILoggerFactory logger) - : base(options, (SignInManager)signInManager, clock, logger) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); - _siteStatService = siteStatService ?? throw new ArgumentNullException(nameof(siteStatService)); - _clock = clock; - } + public TimeSpan UpdateLastModifiedDate { get; set; } = TimeSpan.FromMinutes(2); - public TimeSpan UpdateLastModifiedDate { get; set; } = TimeSpan.FromMinutes(2); + public override async Task ValidateAsync(CookieValidatePrincipalContext context) + { + await base.ValidateAsync(context); + await UpdateUserLastVisitDateTimeAsync(context); + } - public override async Task ValidateAsync(CookieValidatePrincipalContext context) + private async Task UpdateUserLastVisitDateTimeAsync(CookieValidatePrincipalContext context) + { + if (context == null) { - await base.ValidateAsync(context); - await updateUserLastVisitDateTimeAsync(context); + throw new ArgumentNullException(nameof(context)); } - private async Task updateUserLastVisitDateTimeAsync(CookieValidatePrincipalContext context) + var currentUtc = DateTimeOffset.UtcNow; + if (context.Options != null && _clock != null) { - var currentUtc = DateTimeOffset.UtcNow; - if (context.Options != null && _clock != null) - { - currentUtc = _clock.UtcNow; - } - var issuedUtc = context.Properties.IssuedUtc; + currentUtc = _clock.UtcNow; + } - // Only validate if enough time has elapsed - if (issuedUtc == null || context.Principal == null) - { - return; - } + var issuedUtc = context.Properties.IssuedUtc; - var timeElapsed = currentUtc.Subtract(issuedUtc.Value); - if (timeElapsed > UpdateLastModifiedDate) - { - await _siteStatService.UpdateUserLastVisitDateTimeAsync(context.Principal); - } + // Only validate if enough time has elapsed + if (issuedUtc == null || context.Principal == null) + { + return; + } + + var timeElapsed = currentUtc.Subtract(issuedUtc.Value); + if (timeElapsed > UpdateLastModifiedDate) + { + await _siteStatService.UpdateUserLastVisitDateTimeAsync(context.Principal); } } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/CustomUserValidator.cs b/src/ASPNETCoreIdentitySample.Services/Identity/CustomUserValidator.cs index caf6557..40be5fb 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/CustomUserValidator.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/CustomUserValidator.cs @@ -1,109 +1,107 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using ASPNETCoreIdentitySample.Common.GuardToolkit; using ASPNETCoreIdentitySample.Entities.Identity; -using Microsoft.AspNetCore.Identity; -using ASPNETCoreIdentitySample.Common.GuardToolkit; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; +using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// Extending the Built-in User Validation +/// More info: http://www.dntips.ir/post/2579 +/// +public class CustomUserValidator : UserValidator { - /// - /// Extending the Built-in User Validation - /// More info: http://www.dotnettips.info/post/2579 - /// - public class CustomUserValidator : UserValidator - { - private readonly ISet _emailsBanList; + 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) { - if (configurationRoot == null) throw new ArgumentNullException(nameof(configurationRoot)); - _emailsBanList = new HashSet(configurationRoot.Value.EmailsBanList, StringComparer.OrdinalIgnoreCase); - - if (!_emailsBanList.Any()) - { - throw new InvalidOperationException("Please fill the emails ban list in the appsettings.json file."); - } + throw new ArgumentNullException(nameof(configurationRoot)); } - public override async Task ValidateAsync(UserManager manager, User user) + _emailsBanList = new HashSet(configurationRoot.Value.EmailsBanList, StringComparer.OrdinalIgnoreCase); + + if (!_emailsBanList.Any()) { - // First use the built-in validator - var result = await base.ValidateAsync(manager, user); - var errors = result.Succeeded ? new List() : result.Errors.ToList(); + throw new InvalidOperationException("Please fill the emails ban list in the appsettings.json file."); + } + } - // Extending the built-in validator - validateEmail(user, errors); - validateUserName(user, errors); + public override async Task ValidateAsync(UserManager manager, User user) + { + // First use the built-in validator + var result = await base.ValidateAsync(manager, user); + var errors = result.Succeeded ? new List() : result.Errors.ToList(); - return !errors.Any() ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray()); - } + // Extending the built-in validator + ValidateEmail(user, errors); + ValidateUserName(user, errors); - private void validateEmail(User user, List errors) + return !errors.Any() ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray()); + } + + private void ValidateEmail(User user, List errors) + { + var userEmail = user?.Email; + if (string.IsNullOrWhiteSpace(userEmail)) { - var userEmail = user?.Email; if (string.IsNullOrWhiteSpace(userEmail)) - { - if (string.IsNullOrWhiteSpace(userEmail)) - { - errors.Add(new IdentityError - { - Code = "EmailIsNotSet", - Description = "لطفا اطلاعات ایمیل را تکمیل کنید." - }); - } - return; // base.ValidateAsync() will cover this case - } - - if (_emailsBanList.Any(email => userEmail.EndsWith(email, StringComparison.OrdinalIgnoreCase))) { errors.Add(new IdentityError { - Code = "BadEmailDomainError", - Description = "لطفا یک ایمیل پروایدر معتبر را وارد نمائید." + Code = "EmailIsNotSet", + Description = "لطفا اطلاعات ایمیل را تکمیل کنید." }); } + + return; // base.ValidateAsync() will cover this case } - private static void validateUserName(User user, List errors) + if (_emailsBanList.Any(email => userEmail.EndsWith(email, StringComparison.OrdinalIgnoreCase))) { - var userName = user?.UserName; - if (string.IsNullOrWhiteSpace(userName)) + errors.Add(new IdentityError { - if (string.IsNullOrWhiteSpace(userName)) - { - errors.Add(new IdentityError - { - Code = "UserIsNotSet", - Description = "لطفا اطلاعات کاربری را تکمیل کنید." - }); - } - return; // base.ValidateAsync() will cover this case - } + Code = "BadEmailDomainError", + Description = "لطفا یک ایمیل پروایدر معتبر را وارد نمائید." + }); + } + } - if (userName.IsNumeric() || userName.ContainsNumber()) + private static void ValidateUserName(User user, List errors) + { + var userName = user?.UserName; + if (string.IsNullOrWhiteSpace(userName)) + { + errors.Add(new IdentityError { - errors.Add(new IdentityError - { - Code = "BadUserNameError", - Description = "نام کاربری وارد شده نمی‌تواند حاوی اعداد باشد." - }); - } + Code = "UserIsNotSet", + Description = "لطفا اطلاعات کاربری را تکمیل کنید." + }); - if (userName.HasConsecutiveChars()) + return; // base.ValidateAsync() will cover this case + } + + if (userName.IsNumeric() || userName.ContainsNumber()) + { + errors.Add(new IdentityError { - errors.Add(new IdentityError - { - Code = "BadUserNameError", - Description = "نام کاربری وارد شده معتبر نیست." - }); - } + Code = "BadUserNameError", + Description = "نام کاربری وارد شده نمی‌تواند حاوی اعداد باشد." + }); + } + + if (userName.HasConsecutiveChars()) + { + errors.Add(new IdentityError + { + Code = "BadUserNameError", + Description = "نام کاربری وارد شده معتبر نیست." + }); } } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/DataProtectionKeyService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/DataProtectionKeyService.cs index a9c15ef..2518808 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/DataProtectionKeyService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/DataProtectionKeyService.cs @@ -1,77 +1,74 @@ -using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Xml.Linq; using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.Entities.Identity; -using Microsoft.AspNetCore.DataProtection.Repositories; using DNTCommon.Web.Core; +using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2717/ +/// +public class DataProtectionKeyService : IXmlRepository { - /// - /// More info: http://www.dotnettips.info/post/2717/ - /// - public class DataProtectionKeyService : IXmlRepository - { - private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; - public DataProtectionKeyService(IServiceProvider serviceProvider, ILogger logger) - { - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - _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)); + } - public IReadOnlyCollection GetAllElements() + public IReadOnlyCollection GetAllElements() + { + return _serviceProvider.RunScopedService>(context => { - return _serviceProvider.RunScopedService>(context => - { - var dataProtectionKeys = context.Set().AsNoTracking(); - var logger = _logger; - return dataProtectionKeys.Select(key => tryParseKeyXml(key.XmlData, logger)).ToList().AsReadOnly(); - }); - } + var dataProtectionKeys = context.Set().AsNoTracking(); + var logger = _logger; + return dataProtectionKeys.Select(key => TryParseKeyXml(key.XmlData, logger)).ToList().AsReadOnly(); + }); + } - private static XElement tryParseKeyXml(string xml, ILogger logger) + 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 => { - try + var dataProtectionKeys = context.Set(); + var entity = dataProtectionKeys.SingleOrDefault(k => k.FriendlyName == friendlyName); + if (entity != null) { - return XElement.Parse(xml); + entity.XmlData = element.ToString(); + dataProtectionKeys.Update(entity); } - catch (Exception e) + else { - logger.LogWarning($"An exception occurred while parsing the key xml '{xml}'.", e); - return null; + dataProtectionKeys.Add(new AppDataProtectionKey + { + FriendlyName = friendlyName, + XmlData = element.ToString(SaveOptions.DisableFormatting) + }); } - } - public void StoreElement(XElement element, string friendlyName) + context.SaveChanges(); + }); + } + + private static XElement TryParseKeyXml(string xml, ILogger logger) + { + try { - // 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 - { - dataProtectionKeys.Add(new AppDataProtectionKey - { - FriendlyName = friendlyName, - XmlData = element.ToString(SaveOptions.DisableFormatting) - }); - } - context.SaveChanges(); - }); + return XElement.Parse(xml); + } + catch (Exception e) + { + logger.LogWarningMessage($"An exception occurred while parsing the key xml '{xml}': {e}."); + return null; } } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/DistributedCacheTicketStore.cs b/src/ASPNETCoreIdentitySample.Services/Identity/DistributedCacheTicketStore.cs index 9a49a1d..7470ec9 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/DistributedCacheTicketStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/DistributedCacheTicketStore.cs @@ -1,62 +1,63 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.Caching.Distributed; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2581 +/// And http://www.dntips.ir/post/2575 +/// +public class DistributedCacheTicketStore : ITicketStore { - /// - /// More info: http://www.dotnettips.info/post/2581 - /// And http://www.dotnettips.info/post/2575 - /// - public class DistributedCacheTicketStore : ITicketStore + private const string KeyPrefix = "AuthSessionStore-"; + private readonly IDistributedCache _cache; + private readonly IDataSerializer _ticketSerializer = TicketSerializer.Default; + + public DistributedCacheTicketStore(IDistributedCache cache) { - private const string KeyPrefix = "AuthSessionStore-"; - private readonly IDistributedCache _cache; - private readonly IDataSerializer _ticketSerializer = TicketSerializer.Default; + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + } - public DistributedCacheTicketStore(IDistributedCache cache) - { - _cache = cache ?? throw new ArgumentNullException(nameof(_cache)); - } + public async Task StoreAsync(AuthenticationTicket ticket) + { + var key = $"{KeyPrefix}{Guid.NewGuid().ToString("N")}"; + await RenewAsync(key, ticket); + return key; + } - public async Task StoreAsync(AuthenticationTicket ticket) + public Task RenewAsync(string key, AuthenticationTicket ticket) + { + if (ticket == null) { - var key = $"{KeyPrefix}{Guid.NewGuid().ToString("N")}"; - await RenewAsync(key, ticket); - return key; + throw new ArgumentNullException(nameof(ticket)); } + // NOTE: Using `services.enableImmediateLogout();` will cause this method to be called per each request. - public Task RenewAsync(string key, AuthenticationTicket 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); - } + var options = new DistributedCacheEntryOptions(); - if (ticket.Properties.AllowRefresh ?? false) - { - options.SetSlidingExpiration(TimeSpan.FromMinutes(30)); // TODO: configurable. - } - - return _cache.SetAsync(key, _ticketSerializer.Serialize(ticket), options); - } - - public async Task RetrieveAsync(string key) + var expiresUtc = ticket.Properties.ExpiresUtc; + if (expiresUtc.HasValue) { - var value = await _cache.GetAsync(key); - return value != null ? _ticketSerializer.Deserialize(value) : null; + options.SetAbsoluteExpiration(expiresUtc.Value); } - public Task RemoveAsync(string key) + if (ticket.Properties.AllowRefresh ?? false) { - return _cache.RemoveAsync(key); + options.SetSlidingExpiration(TimeSpan.FromMinutes(30)); // TODO: configurable. } + + return _cache.SetAsync(key, _ticketSerializer.Serialize(ticket), options); + } + + 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); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionRequirement.cs b/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionRequirement.cs new file mode 100644 index 0000000..d2345fa --- /dev/null +++ b/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionRequirement.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Authorization; + +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2581 +/// +public class DynamicPermissionRequirement : IAuthorizationRequirement +{ +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionsAuthorizationHandler.cs b/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionsAuthorizationHandler.cs index 5445bf1..00d80e7 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionsAuthorizationHandler.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/DynamicPermissionsAuthorizationHandler.cs @@ -1,81 +1,76 @@ using ASPNETCoreIdentitySample.Services.Contracts.Identity; -using ASPNETCoreIdentitySample.ViewModels.Identity; using Microsoft.AspNetCore.Authorization; -using System.Threading.Tasks; -using System; -using DNTCommon.Web.Core; -using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +public class DynamicPermissionsAuthorizationHandler : AuthorizationHandler { - /// - /// More info: http://www.dotnettips.info/post/2581 - /// - public class DynamicPermissionRequirement : IAuthorizationRequirement + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ISecurityTrimmingService _securityTrimmingService; + + public DynamicPermissionsAuthorizationHandler( + ISecurityTrimmingService securityTrimmingService, + IHttpContextAccessor httpContextAccessor) { + _securityTrimmingService = + securityTrimmingService ?? throw new ArgumentNullException(nameof(securityTrimmingService)); + _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); } - public class DynamicPermissionsAuthorizationHandler : AuthorizationHandler + protected override Task HandleRequirementAsync( + AuthorizationHandlerContext context, + DynamicPermissionRequirement requirement) { - private readonly ISecurityTrimmingService _securityTrimmingService; - private readonly IHttpContextAccessor _httpContextAccessor; - - public DynamicPermissionsAuthorizationHandler( - ISecurityTrimmingService securityTrimmingService, - IHttpContextAccessor httpContextAccessor) + if (context == null) { - _securityTrimmingService = securityTrimmingService ?? throw new ArgumentNullException(nameof(securityTrimmingService)); - _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); + throw new ArgumentNullException(nameof(context)); } - protected override async Task HandleRequirementAsync( - AuthorizationHandlerContext context, - DynamicPermissionRequirement requirement) - { - var routeData = _httpContextAccessor.HttpContext.GetRouteData(); + var routeData = _httpContextAccessor.HttpContext?.GetRouteData(); - var areaName = routeData?.Values["area"]?.ToString(); - var area = string.IsNullOrWhiteSpace(areaName) ? string.Empty : areaName; + var areaName = routeData?.Values["area"]?.ToString(); + var area = string.IsNullOrWhiteSpace(areaName) ? string.Empty : areaName; - var controllerName = routeData?.Values["controller"]?.ToString(); - var controller = string.IsNullOrWhiteSpace(controllerName) ? string.Empty : controllerName; + var controllerName = routeData?.Values["controller"]?.ToString(); + var controller = string.IsNullOrWhiteSpace(controllerName) ? string.Empty : controllerName; - var actionName = routeData?.Values["action"]?.ToString(); - var action = string.IsNullOrWhiteSpace(actionName) ? string.Empty : actionName; + var actionName = routeData?.Values["action"]?.ToString(); + var action = string.IsNullOrWhiteSpace(actionName) ? string.Empty : actionName; - // This is just a sample: How to access form values from an AuthorizationHandler - /*var request = _httpContextAccessor.HttpContext.Request; - if (request.Method.Equals("post", StringComparison.OrdinalIgnoreCase)) + // This is just a sample: How to access form values from an AuthorizationHandler + /*var request = _httpContextAccessor.HttpContext.Request; + if (request.Method.Equals("post", StringComparison.OrdinalIgnoreCase)) + { + if (request.IsAjaxRequest() && request.ContentType.Contains("application/json")) { - if (request.IsAjaxRequest() && request.ContentType.Contains("application/json")) + var httpRequestInfoService = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService(); + var model = await httpRequestInfoService.DeserializeRequestJsonBodyAsAsync(); + if (model != null) { - var httpRequestInfoService = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService(); - var model = await httpRequestInfoService.DeserializeRequestJsonBodyAsAsync(); - if (model != null) - { - } - } - else - { - foreach (var item in request.Form) - { - var formField = item.Key; - var formFieldValue = item.Value; - } } - }*/ - - if (_securityTrimmingService.CanCurrentUserAccess(area, controller, action)) - { - context.Succeed(requirement); } else { - context.Fail(); + foreach (var item in request.Form) + { + var formField = item.Key; + var formFieldValue = item.Value; + } } + }*/ + + if (_securityTrimmingService.CanCurrentUserAccess(area, controller, action)) + { + context.Succeed(requirement); + } + else + { + context.Fail(); } + + return Task.CompletedTask; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/IdentityDbInitializer.cs b/src/ASPNETCoreIdentitySample.Services/Identity/IdentityDbInitializer.cs index d9ede7c..1ce1641 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/IdentityDbInitializer.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/IdentityDbInitializer.cs @@ -1,166 +1,164 @@ -using ASPNETCoreIdentitySample.Common.GuardToolkit; -using ASPNETCoreIdentitySample.Common.IdentityToolkit; +using ASPNETCoreIdentitySample.Common.IdentityToolkit; using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Linq; -using System.Threading.Tasks; -using System; -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2577 +/// And http://www.dntips.ir/post/2578 +/// +public class IdentityDbInitializer : IIdentityDbInitializer { - /// - /// More info: http://www.dotnettips.info/post/2577 - /// And http://www.dotnettips.info/post/2578 - /// - public class IdentityDbInitializer : IIdentityDbInitializer + private readonly IOptionsSnapshot _adminUserSeedOptions; + private readonly IApplicationUserManager _applicationUserManager; + private readonly ILogger _logger; + private readonly IApplicationRoleManager _roleManager; + private readonly IServiceScopeFactory _scopeFactory; + + public IdentityDbInitializer( + IApplicationUserManager applicationUserManager, + IServiceScopeFactory scopeFactory, + IApplicationRoleManager roleManager, + IOptionsSnapshot adminUserSeedOptions, + ILogger logger + ) { - private readonly IOptionsSnapshot _adminUserSeedOptions; - private readonly IApplicationUserManager _applicationUserManager; - private readonly ILogger _logger; - private readonly IApplicationRoleManager _roleManager; - private readonly IServiceScopeFactory _scopeFactory; - - public IdentityDbInitializer( - IApplicationUserManager applicationUserManager, - IServiceScopeFactory scopeFactory, - IApplicationRoleManager roleManager, - IOptionsSnapshot adminUserSeedOptions, - ILogger logger - ) - { - _applicationUserManager = applicationUserManager; - _applicationUserManager.CheckArgumentIsNull(nameof(_applicationUserManager)); + _applicationUserManager = applicationUserManager; + ArgumentNullException.ThrowIfNull(applicationUserManager); - _scopeFactory = scopeFactory; - _scopeFactory.CheckArgumentIsNull(nameof(_scopeFactory)); + _scopeFactory = scopeFactory; + ArgumentNullException.ThrowIfNull(scopeFactory); - _roleManager = roleManager; - _roleManager.CheckArgumentIsNull(nameof(_roleManager)); + _roleManager = roleManager; + ArgumentNullException.ThrowIfNull(roleManager); - _adminUserSeedOptions = adminUserSeedOptions; - _adminUserSeedOptions.CheckArgumentIsNull(nameof(_adminUserSeedOptions)); + _adminUserSeedOptions = adminUserSeedOptions; + ArgumentNullException.ThrowIfNull(adminUserSeedOptions); - _logger = logger; - _logger.CheckArgumentIsNull(nameof(_logger)); - } - - /// - /// Applies any pending migrations for the context to the database. - /// Will create the database if it does not already exist. - /// - public void Initialize() - { - _scopeFactory.RunScopedService(context => - { - if (_adminUserSeedOptions.Value.ActiveDatabase == ActiveDatabase.InMemoryDatabase) - { - context.Database.EnsureCreated(); - } - else - { - context.Database.Migrate(); - } - }); - } - - /// - /// Adds some default values to the IdentityDb - /// - public void SeedData() - { - _scopeFactory.RunScopedService(identityDbSeedData => - { - var result = identityDbSeedData.SeedDatabaseWithAdminUserAsync().Result; - if (result == IdentityResult.Failed()) - { - throw new InvalidOperationException(result.DumpErrors()); - } - }); - - _scopeFactory.RunScopedService(context => - { - if (!context.Roles.Any()) - { - context.Add(new Role(ConstantRoles.Admin)); - context.SaveChanges(); - } - }); - } + _logger = logger; + ArgumentNullException.ThrowIfNull(logger); + } - public async Task SeedDatabaseWithAdminUserAsync() + /// + /// Applies any pending migrations for the context to the database. + /// Will create the database if it does not already exist. + /// + public void Initialize() + { + _scopeFactory.RunScopedService(context => { - var adminUserSeed = _adminUserSeedOptions.Value.AdminUserSeed; - adminUserSeed.CheckArgumentIsNull(nameof(adminUserSeed)); - - var name = adminUserSeed.Username; - var password = adminUserSeed.Password; - var email = adminUserSeed.Email; - var roleName = adminUserSeed.RoleName; - - var thisMethodName = nameof(SeedDatabaseWithAdminUserAsync); - - var adminUser = await _applicationUserManager.FindByNameAsync(name); - if (adminUser != null) - { - _logger.LogInformation($"{thisMethodName}: adminUser already exists."); - return IdentityResult.Success; - } - - //Create the `Admin` Role if it does not exist - var adminRole = await _roleManager.FindByNameAsync(roleName); - if (adminRole == null) + if (_adminUserSeedOptions.Value.ActiveDatabase == ActiveDatabase.InMemoryDatabase) { - adminRole = new Role(roleName); - var adminRoleResult = await _roleManager.CreateAsync(adminRole); - if (adminRoleResult == IdentityResult.Failed()) - { - _logger.LogError($"{thisMethodName}: adminRole CreateAsync failed. {adminRoleResult.DumpErrors()}"); - return IdentityResult.Failed(); - } + context.Database.EnsureCreated(); } else { - _logger.LogInformation($"{thisMethodName}: adminRole already exists."); + context.Database.Migrate(); } + }); + } - adminUser = new User - { - UserName = name, - Email = email, - EmailConfirmed = true, - IsEmailPublic = true, - LockoutEnabled = true - }; - var adminUserResult = await _applicationUserManager.CreateAsync(adminUser, password); - if (adminUserResult == IdentityResult.Failed()) + /// + /// Adds some default values to the IdentityDb + /// + public void SeedData() + { + _scopeFactory.RunScopedService(identityDbSeedData => + { + var result = identityDbSeedData.SeedDatabaseWithAdminUserAsync().GetAwaiter().GetResult(); + if (result == IdentityResult.Failed()) { - _logger.LogError($"{thisMethodName}: adminUser CreateAsync failed. {adminUserResult.DumpErrors()}"); - return IdentityResult.Failed(); + throw new InvalidOperationException(result.DumpErrors()); } + }); - var setLockoutResult = await _applicationUserManager.SetLockoutEnabledAsync(adminUser, enabled: false); - if (setLockoutResult == IdentityResult.Failed()) + _scopeFactory.RunScopedService(context => + { + if (!context.Roles.Any()) { - _logger.LogError($"{thisMethodName}: adminUser SetLockoutEnabledAsync failed. {setLockoutResult.DumpErrors()}"); - return IdentityResult.Failed(); + context.Add(new Role(ConstantRoles.Admin)); + context.SaveChanges(); } + }); + } + + public async Task SeedDatabaseWithAdminUserAsync() + { + var adminUserSeed = _adminUserSeedOptions.Value.AdminUserSeed; + ArgumentNullException.ThrowIfNull(adminUserSeed); + + var name = adminUserSeed.Username; + var password = adminUserSeed.Password; + var email = adminUserSeed.Email; + var roleName = adminUserSeed.RoleName; + + var thisMethodName = nameof(SeedDatabaseWithAdminUserAsync); + + var adminUser = await _applicationUserManager.FindByNameAsync(name); + if (adminUser != null) + { + _logger.LogInformationMessage($"{thisMethodName}: adminUser already exists."); + return IdentityResult.Success; + } - var addToRoleResult = await _applicationUserManager.AddToRoleAsync(adminUser, adminRole.Name); - if (addToRoleResult == IdentityResult.Failed()) + //Create the `Admin` Role if it does not exist + var adminRole = await _roleManager.FindByNameAsync(roleName); + if (adminRole == null) + { + adminRole = new Role(roleName); + var adminRoleResult = await _roleManager.CreateAsync(adminRole); + if (adminRoleResult == IdentityResult.Failed()) { - _logger.LogError($"{thisMethodName}: adminUser AddToRoleAsync failed. {addToRoleResult.DumpErrors()}"); + _logger.LogErrorMessage( + $"{thisMethodName}: adminRole CreateAsync failed. {adminRoleResult.DumpErrors()}"); return IdentityResult.Failed(); } + } + else + { + _logger.LogInformationMessage($"{thisMethodName}: adminRole already exists."); + } - return IdentityResult.Success; + adminUser = new User + { + UserName = name, + Email = email, + EmailConfirmed = true, + IsEmailPublic = true, + LockoutEnabled = true + }; + var adminUserResult = await _applicationUserManager.CreateAsync(adminUser, password); + if (adminUserResult == IdentityResult.Failed()) + { + _logger.LogErrorMessage($"{thisMethodName}: adminUser CreateAsync failed. {adminUserResult.DumpErrors()}"); + return IdentityResult.Failed(); } + + var setLockoutResult = await _applicationUserManager.SetLockoutEnabledAsync(adminUser, false); + if (setLockoutResult == IdentityResult.Failed()) + { + _logger.LogErrorMessage( + $"{thisMethodName}: adminUser SetLockoutEnabledAsync failed. {setLockoutResult.DumpErrors()}"); + return IdentityResult.Failed(); + } + + var addToRoleResult = await _applicationUserManager.AddToRoleAsync(adminUser, adminRole.Name); + if (addToRoleResult == IdentityResult.Failed()) + { + _logger.LogErrorMessage( + $"{thisMethodName}: adminUser AddToRoleAsync failed. {addToRoleResult.DumpErrors()}"); + return IdentityResult.Failed(); + } + + return IdentityResult.Success; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/AppLogItemsService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/AppLogItemsService.cs index e7fc1f7..10cf787 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/AppLogItemsService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/AppLogItemsService.cs @@ -3,93 +3,89 @@ using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; using Microsoft.EntityFrameworkCore; -using System.Linq; -using System.Threading.Tasks; -using System; -namespace ASPNETCoreIdentitySample.Services.Identity.Logger +namespace ASPNETCoreIdentitySample.Services.Identity.Logger; + +public class AppLogItemsService : IAppLogItemsService { - public class AppLogItemsService : IAppLogItemsService + private readonly DbSet _appLogItems; + private readonly IUnitOfWork _uow; + + public AppLogItemsService(IUnitOfWork uow) { - private readonly DbSet _appLogItems; - private readonly IUnitOfWork _uow; + _uow = uow ?? throw new ArgumentNullException(nameof(uow)); + _appLogItems = _uow.Set(); + } - public AppLogItemsService(IUnitOfWork uow) + public Task DeleteAllAsync(string logLevel = "") + { + if (string.IsNullOrWhiteSpace(logLevel)) { - _uow = uow ?? throw new ArgumentNullException(nameof(_uow)); - _appLogItems = _uow.Set(); + _appLogItems.RemoveRange(_appLogItems); } - - public Task DeleteAllAsync(string logLevel = "") + else { - if (string.IsNullOrWhiteSpace(logLevel)) - { - _appLogItems.RemoveRange(_appLogItems); - } - else - { - var query = _appLogItems.Where(l => l.LogLevel == logLevel); - _appLogItems.RemoveRange(query); - } - - return _uow.SaveChangesAsync(); + var query = _appLogItems.Where(l => l.LogLevel == logLevel); + _appLogItems.RemoveRange(query); } - public async Task DeleteAsync(int logItemId) + return _uow.SaveChangesAsync(); + } + + public async Task DeleteAsync(int logItemId) + { + var itemToRemove = await _appLogItems.FirstOrDefaultAsync(x => x.Id.Equals(logItemId)); + if (itemToRemove != null) { - var itemToRemove = await _appLogItems.FirstOrDefaultAsync(x => x.Id.Equals(logItemId)); - if (itemToRemove != null) - { - _appLogItems.Remove(itemToRemove); - await _uow.SaveChangesAsync(); - } + _appLogItems.Remove(itemToRemove); + await _uow.SaveChangesAsync(); } + } - public Task DeleteOlderThanAsync(DateTime cutoffDateUtc, string logLevel = "") + public Task DeleteOlderThanAsync(DateTime cutoffDateUtc, string logLevel = "") + { + if (string.IsNullOrWhiteSpace(logLevel)) { - if (string.IsNullOrWhiteSpace(logLevel)) - { - var query = _appLogItems.Where(l => l.CreatedDateTime < cutoffDateUtc); - _appLogItems.RemoveRange(query); - } - else - { - var query = _appLogItems.Where(l => l.CreatedDateTime < cutoffDateUtc && l.LogLevel == logLevel); - _appLogItems.RemoveRange(query); - } - - return _uow.SaveChangesAsync(); + var query = _appLogItems.Where(l => l.CreatedDateTime < cutoffDateUtc); + _appLogItems.RemoveRange(query); } - - public Task GetCountAsync(string logLevel = "") + else { - return string.IsNullOrWhiteSpace(logLevel) ? - _appLogItems.CountAsync() : - _appLogItems.Where(l => l.LogLevel == logLevel).CountAsync(); + var query = _appLogItems.Where(l => l.CreatedDateTime < cutoffDateUtc && l.LogLevel == logLevel); + _appLogItems.RemoveRange(query); } - public async Task GetPagedAppLogItemsAsync( - int pageNumber, - int pageSize, - SortOrder sortOrder, - string logLevel = "") - { - var offset = (pageSize * pageNumber) - pageSize; + return _uow.SaveChangesAsync(); + } - var query = string.IsNullOrWhiteSpace(logLevel) ? - _appLogItems : - _appLogItems.Where(l => l.LogLevel == logLevel); + public Task GetCountAsync(string logLevel = "") + { + return string.IsNullOrWhiteSpace(logLevel) + ? _appLogItems.CountAsync() + : _appLogItems.Where(l => l.LogLevel == logLevel).CountAsync(); + } + + public async Task GetPagedAppLogItemsAsync( + int pageNumber, + int pageSize, + SortOrder sortOrder, + string logLevel = "") + { + var offset = pageSize * pageNumber - pageSize; - query = sortOrder == SortOrder.Descending ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id); + var query = string.IsNullOrWhiteSpace(logLevel) + ? _appLogItems + : _appLogItems.Where(l => l.LogLevel == logLevel); - return new PagedAppLogItemsViewModel + query = sortOrder == SortOrder.Descending ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id); + + return new PagedAppLogItemsViewModel + { + Paging = { - Paging = - { - TotalItems = await query.CountAsync() - }, - AppLogItems = await query.Skip(offset).Take(pageSize).ToListAsync() - }; - } + TotalItems = await query.CountAsync() + }, + AppLogItems = await query.Skip(offset).Take(pageSize).ToListAsync() + }; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLogger.cs b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLogger.cs index 05b02b1..8518d09 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLogger.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLogger.cs @@ -5,108 +5,112 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System; -using System.Text.Json; -namespace ASPNETCoreIdentitySample.Services.Identity.Logger +namespace ASPNETCoreIdentitySample.Services.Identity.Logger; + +public class DbLogger : ILogger { - public class DbLogger : ILogger + private readonly string _loggerName; + private readonly DbLoggerProvider _loggerProvider; + private readonly LogLevel _minLevel; + private readonly IServiceProvider _serviceProvider; + + public DbLogger( + DbLoggerProvider loggerProvider, + IServiceProvider serviceProvider, + string loggerName, + IOptions siteSettings) + { + _loggerName = loggerName; + ArgumentNullException.ThrowIfNull(siteSettings); + _minLevel = siteSettings.Value.Logging.LogLevel.Default; + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _loggerProvider = loggerProvider ?? throw new ArgumentNullException(nameof(loggerProvider)); + } + + public IDisposable BeginScope(TState state) + { + return new NoopDisposable(); + } + + public bool IsEnabled(LogLevel logLevel) { - private readonly string _loggerName; - private readonly IServiceProvider _serviceProvider; - private readonly DbLoggerProvider _loggerProvider; - private readonly IOptions _siteSettings; - private readonly LogLevel _minLevel; + return logLevel >= _minLevel; + } - public DbLogger( - DbLoggerProvider loggerProvider, - IServiceProvider serviceProvider, - string loggerName, - IOptions siteSettings) + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception exception, + Func formatter) + { + if (!IsEnabled(logLevel)) { - _loggerName = loggerName; - _siteSettings = siteSettings ?? throw new ArgumentNullException(nameof(siteSettings)); - _minLevel = _siteSettings.Value.Logging.LogLevel.Default; - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - _loggerProvider = loggerProvider ?? throw new ArgumentNullException(nameof(loggerProvider)); + return; } - public IDisposable BeginScope(TState state) + if (formatter == null) { - return new NoopDisposable(); + throw new ArgumentNullException(nameof(formatter)); } - public bool IsEnabled(LogLevel logLevel) + var message = formatter(state, exception); + + if (exception != null) { - return logLevel >= _minLevel; + message = $"{message}{Environment.NewLine}{exception}"; } - public void Log( - LogLevel logLevel, - EventId eventId, - TState state, - Exception exception, - Func formatter) + if (string.IsNullOrEmpty(message)) { - if (!IsEnabled(logLevel)) - { - return; - } - - if (formatter == null) - { - throw new ArgumentNullException(nameof(formatter)); - } - - var message = formatter(state, exception); - - if (exception != null) - { - message = $"{message}{Environment.NewLine}{exception}"; - } + return; + } - if (string.IsNullOrEmpty(message)) - { - return; - } + 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 + }; + var props = httpContextAccessor?.GetShadowProperties(); + SetStateJson(state, appLogItem); + _loggerProvider.AddLogItem(new LoggerItem { Props = props, AppLogItem = appLogItem }); + } - 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 - }; - var props = httpContextAccessor?.GetShadowProperties(); - setStateJson(state, appLogItem); - _loggerProvider.AddLogItem(new LoggerItem { Props = props, AppLogItem = appLogItem }); + private static void SetStateJson(TState state, AppLogItem appLogItem) + { + try + { + appLogItem.StateJson = JsonSerializer.Serialize( + state, + new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = true + }); + } + catch + { + // don't throw exceptions from logger } + } - private static void setStateJson(TState state, AppLogItem appLogItem) + private class NoopDisposable : IDisposable + { + public void Dispose() { - try - { - appLogItem.StateJson = JsonSerializer.Serialize( - state, - new JsonSerializerOptions - { - IgnoreNullValues = true, - WriteIndented = true - }); - } - catch - { - // don't throw exceptions from logger - } + Dispose(true); + GC.SuppressFinalize(this); } - private class NoopDisposable : IDisposable + protected virtual void Dispose(bool disposing) { - public void Dispose() - { - } } } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerFactoryExtensions.cs b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerFactoryExtensions.cs index 4a59656..f60d12b 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerFactoryExtensions.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerFactoryExtensions.cs @@ -1,14 +1,18 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace ASPNETCoreIdentitySample.Services.Identity.Logger +namespace ASPNETCoreIdentitySample.Services.Identity.Logger; + +public static class DbLoggerFactoryExtensions { - public static class DbLoggerFactoryExtensions + public static ILoggingBuilder AddDbLogger(this ILoggingBuilder builder) { - public static ILoggingBuilder AddDbLogger(this ILoggingBuilder builder) + if (builder == null) { - builder.Services.AddSingleton(); - return builder; + throw new ArgumentNullException(nameof(builder)); } + + builder.Services.AddSingleton(); + return builder; } } \ 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 7e10bcd..8fe614e 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerProvider.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/DbLoggerProvider.cs @@ -1,133 +1,140 @@ -using ASPNETCoreIdentitySample.DataLayer.Context; +using System.Collections.Concurrent; +using ASPNETCoreIdentitySample.DataLayer.Context; +using ASPNETCoreIdentitySample.Entities.AuditableEntity; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; +using DNTCommon.Web.Core; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using ASPNETCoreIdentitySample.Entities.AuditableEntity; -using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample.Services.Identity.Logger +namespace ASPNETCoreIdentitySample.Services.Identity.Logger; + +public class DbLoggerProvider : ILoggerProvider { - public class LoggerItem - { - public AppShadowProperties Props { set; get; } - public AppLogItem AppLogItem { set; get; } - } + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private readonly IList _currentBatch = new List(); + private readonly TimeSpan _interval = TimeSpan.FromSeconds(2); - public class DbLoggerProvider : ILoggerProvider - { - private readonly TimeSpan _interval = TimeSpan.FromSeconds(2); - private readonly IServiceProvider _serviceProvider; - private readonly IList _currentBatch = new List(); + private readonly BlockingCollection _messageQueue = new(new ConcurrentQueue()); - private readonly BlockingCollection _messageQueue = - new BlockingCollection(new ConcurrentQueue()); + private readonly Task _outputTask; + private readonly IServiceProvider _serviceProvider; + private readonly IOptions _siteSettings; + private bool _isDisposed; - private readonly Task _outputTask; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private readonly IOptions _siteSettings; + public DbLoggerProvider( + IOptions siteSettings, + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _siteSettings = siteSettings ?? throw new ArgumentNullException(nameof(siteSettings)); + _outputTask = Task.Run(ProcessLogQueue); + } - public DbLoggerProvider( - IOptions siteSettings, - IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(_serviceProvider)); - _siteSettings = siteSettings ?? throw new ArgumentNullException(nameof(_siteSettings)); - _outputTask = Task.Run(processLogQueue); - } + public ILogger CreateLogger(string categoryName) + { + return new DbLogger(this, _serviceProvider, categoryName, _siteSettings); + } - public ILogger CreateLogger(string categoryName) - { - return new DbLogger(this, _serviceProvider, categoryName, _siteSettings); - } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - public void Dispose() + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) { - stop(); - _messageQueue.Dispose(); - _cancellationTokenSource.Dispose(); + try + { + if (disposing) + { + Stop(); + _messageQueue.Dispose(); + _cancellationTokenSource.Dispose(); + } + } + finally + { + _isDisposed = true; + } } + } - internal void AddLogItem(LoggerItem appLogItem) + internal void AddLogItem(LoggerItem appLogItem) + { + if (!_messageQueue.IsAddingCompleted) { - if (!_messageQueue.IsAddingCompleted) - { - _messageQueue.Add(appLogItem, _cancellationTokenSource.Token); - } + _messageQueue.Add(appLogItem, _cancellationTokenSource.Token); } + } - private async Task processLogQueue() + private async Task ProcessLogQueue() + { + while (!_cancellationTokenSource.IsCancellationRequested) { - while (!_cancellationTokenSource.IsCancellationRequested) + while (_messageQueue.TryTake(out var message)) { - while (_messageQueue.TryTake(out var message)) + try + { + _currentBatch.Add(message); + } + catch { - try - { - _currentBatch.Add(message); - } - catch - { - //cancellation token canceled or CompleteAdding called - } + //cancellation token canceled or CompleteAdding called } + } - await saveLogItemsAsync(_currentBatch, _cancellationTokenSource.Token); - _currentBatch.Clear(); + await SaveLogItemsAsync(_currentBatch, _cancellationTokenSource.Token); + _currentBatch.Clear(); - await Task.Delay(_interval, _cancellationTokenSource.Token); - } + await Task.Delay(_interval, _cancellationTokenSource.Token); } + } - private async Task saveLogItemsAsync(IList items, CancellationToken cancellationToken) + private async Task SaveLogItemsAsync(IList items, CancellationToken cancellationToken) + { + try { - try + if (!items.Any()) { - if (!items.Any()) + return; + } + + // We need a separate context for the logger to call its SaveChanges several times, + // without using the current request's context and changing its internal state. + await _serviceProvider.RunScopedServiceAsync(async context => + { + foreach (var item in items) { - return; + var addedEntry = context.Set().Add(item.AppLogItem); + addedEntry.SetAddedShadowProperties(item.Props); } - // We need a separate context for the logger to call its SaveChanges several times, - // without using the current request's context and changing its internal state. - await _serviceProvider.RunScopedServiceAsync(async context => - { - foreach (var item in items) - { - var addedEntry = context.Set().Add(item.AppLogItem); - addedEntry.SetAddedShadowProperties(item.Props); - } - await context.SaveChangesAsync(cancellationToken); - }); - } - catch - { - // don't throw exceptions from logger - } + await context.SaveChangesAsync(cancellationToken); + }); } - - private void stop() + catch { - _cancellationTokenSource.Cancel(); - _messageQueue.CompleteAdding(); + // don't throw exceptions from logger + } + } - try - { - _outputTask.Wait(_interval); - } - catch (TaskCanceledException) - { - } - catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && - ex.InnerExceptions[0] is TaskCanceledException) - { - } + [SuppressMessage("Microsoft.Usage", "CA1031:catch a more specific allowed exception type, or rethrow the exception", + Justification = "don't throw exceptions from logger")] + private void Stop() + { + _cancellationTokenSource.Cancel(); + _messageQueue.CompleteAdding(); + + try + { + _outputTask.Wait(_interval); + } + catch + { + // don't throw exceptions from logger } } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/Logger/LoggerItem.cs b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/LoggerItem.cs new file mode 100644 index 0000000..ce12841 --- /dev/null +++ b/src/ASPNETCoreIdentitySample.Services/Identity/Logger/LoggerItem.cs @@ -0,0 +1,10 @@ +using ASPNETCoreIdentitySample.Entities.AuditableEntity; +using ASPNETCoreIdentitySample.Entities.Identity; + +namespace ASPNETCoreIdentitySample.Services.Identity.Logger; + +public class LoggerItem +{ + public AppShadowProperties Props { set; get; } + public AppLogItem AppLogItem { set; get; } +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/MemoryCacheTicketStore.cs b/src/ASPNETCoreIdentitySample.Services/Identity/MemoryCacheTicketStore.cs index 6d5cf06..852a6b3 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/MemoryCacheTicketStore.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/MemoryCacheTicketStore.cs @@ -1,64 +1,66 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.Caching.Memory; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// Adapted from https://github.com/aspnet/Security/blob/dev/samples/CookieSessionSample/MemoryCacheTicketStore.cs +/// to manage large identity cookies. +/// More info: http://www.dntips.ir/post/2581 +/// And http://www.dntips.ir/post/2575 +/// +public class MemoryCacheTicketStore : ITicketStore { - /// - /// Adapted from https://github.com/aspnet/Security/blob/dev/samples/CookieSessionSample/MemoryCacheTicketStore.cs - /// to manage large identity cookies. - /// More info: http://www.dotnettips.info/post/2581 - /// And http://www.dotnettips.info/post/2575 - /// - public class MemoryCacheTicketStore : ITicketStore + private const string KeyPrefix = "AuthSessionStore-"; + private readonly IMemoryCache _cache; + + public MemoryCacheTicketStore(IMemoryCache cache) + { + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + } + + public async Task StoreAsync(AuthenticationTicket ticket) { - private const string KeyPrefix = "AuthSessionStore-"; - private readonly IMemoryCache _cache; + var key = $"{KeyPrefix}{Guid.NewGuid().ToString("N")}"; + await RenewAsync(key, ticket); + return key; + } - public MemoryCacheTicketStore(IMemoryCache cache) + public Task RenewAsync(string key, AuthenticationTicket ticket) + { + if (ticket == null) { - _cache = cache ?? throw new ArgumentNullException(nameof(_cache)); + throw new ArgumentNullException(nameof(ticket)); } - public async Task StoreAsync(AuthenticationTicket ticket) + var options = new MemoryCacheEntryOptions().SetSize(1); + var expiresUtc = ticket.Properties.ExpiresUtc; + + if (expiresUtc.HasValue) { - var key = $"{KeyPrefix}{Guid.NewGuid().ToString("N")}"; - await RenewAsync(key, ticket); - return key; + options.SetAbsoluteExpiration(expiresUtc.Value); } - public Task RenewAsync(string key, AuthenticationTicket ticket) + if (ticket.Properties.AllowRefresh ?? false) { - var options = new MemoryCacheEntryOptions().SetSize(1); - var expiresUtc = ticket.Properties.ExpiresUtc; - - if (expiresUtc.HasValue) - { - options.SetAbsoluteExpiration(expiresUtc.Value); - } - - if (ticket.Properties.AllowRefresh ?? false) - { - options.SetSlidingExpiration(TimeSpan.FromMinutes(60));//TODO: configurable. - } + options.SetSlidingExpiration(TimeSpan.FromMinutes(60)); //TODO: configurable. + } - _cache.Set(key, ticket, options); + _cache.Set(key, ticket, options); - return Task.FromResult(0); - } + return Task.FromResult(0); + } - public Task RetrieveAsync(string key) - { - _cache.TryGetValue(key, out AuthenticationTicket ticket); - return Task.FromResult(ticket); - } + 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); - } + public Task RemoveAsync(string key) + { + _cache.Remove(key); + return Task.FromResult(0); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddleware.cs b/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddleware.cs index 19a2e91..f468d01 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddleware.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddleware.cs @@ -1,31 +1,20 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; using DNTCommon.Web.Core; +using Microsoft.AspNetCore.Http; -namespace ASPNETCoreIdentitySample.Services.Identity -{ - public class NoBrowserCacheMiddleware - { - private readonly RequestDelegate _next; +namespace ASPNETCoreIdentitySample.Services.Identity; - public NoBrowserCacheMiddleware(RequestDelegate next) - { - _next = next; - } +public class NoBrowserCacheMiddleware +{ + private readonly RequestDelegate _next; - public Task Invoke(HttpContext context) - { - context.DisableBrowserCache(); - return _next(context); - } + public NoBrowserCacheMiddleware(RequestDelegate next) + { + _next = next; } - public static class NoBrowserCacheMiddlewareExtensions + public Task Invoke(HttpContext context) { - public static IApplicationBuilder UseNoBrowserCache(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } + context.DisableBrowserCache(); + return _next(context); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddlewareExtensions.cs b/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddlewareExtensions.cs new file mode 100644 index 0000000..8073036 --- /dev/null +++ b/src/ASPNETCoreIdentitySample.Services/Identity/NoBrowserCacheMiddlewareExtensions.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Builder; + +namespace ASPNETCoreIdentitySample.Services.Identity; + +public static class NoBrowserCacheMiddlewareExtensions +{ + public static IApplicationBuilder UseNoBrowserCache(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/SecurityTrimmingService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/SecurityTrimmingService.cs index 69bd21a..0371e17 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/SecurityTrimmingService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/SecurityTrimmingService.cs @@ -1,60 +1,60 @@ -using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using System.Security.Claims; +using ASPNETCoreIdentitySample.Services.Contracts.Identity; using DNTCommon.Web.Core; using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +/// +/// More info: http://www.dntips.ir/post/2581 +/// +public class SecurityTrimmingService : ISecurityTrimmingService { - /// - /// More info: http://www.dotnettips.info/post/2581 - /// - public class SecurityTrimmingService : ISecurityTrimmingService + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IMvcActionsDiscoveryService _mvcActionsDiscoveryService; + + public SecurityTrimmingService( + IHttpContextAccessor httpContextAccessor, + IMvcActionsDiscoveryService mvcActionsDiscoveryService) + { + _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); + _mvcActionsDiscoveryService = mvcActionsDiscoveryService ?? + throw new ArgumentNullException(nameof(mvcActionsDiscoveryService)); + } + + public bool CanCurrentUserAccess(string area, string controller, string action) { - private readonly HttpContext _httpContext; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IMvcActionsDiscoveryService _mvcActionsDiscoveryService; + return _httpContextAccessor.HttpContext != null && + CanUserAccess(_httpContextAccessor.HttpContext.User, area, controller, action); + } - public SecurityTrimmingService( - IHttpContextAccessor httpContextAccessor, - IMvcActionsDiscoveryService mvcActionsDiscoveryService) + 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))) { - _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); - _httpContext = _httpContextAccessor.HttpContext; - _mvcActionsDiscoveryService = mvcActionsDiscoveryService ?? throw new ArgumentNullException(nameof(mvcActionsDiscoveryService)); + throw new KeyNotFoundException( + $"The `secured` area={area}/controller={controller}/action={action} with `ConstantPolicies.DynamicPermission` policy not found. Please check you have entered the area/controller/action names correctly and also it's decorated with the correct security policy."); } - public bool CanCurrentUserAccess(string area, string controller, string action) + if (user?.Identity is null || !user.Identity.IsAuthenticated) { - return _httpContext != null && CanUserAccess(_httpContext.User, area, controller, action); + return false; } - public bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action) + if (user.IsInRole(ConstantRoles.Admin)) { - var currentClaimValue = $"{area}:{controller}:{action}"; - var securedControllerActions = _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission); - if (!securedControllerActions.SelectMany(x => x.MvcActions).Any(x => x.ActionId == currentClaimValue)) - { - throw new KeyNotFoundException($"The `secured` area={area}/controller={controller}/action={action} with `ConstantPolicies.DynamicPermission` policy not found. Please check you have entered the area/controller/action names correctly and also it's decorated with the correct security policy."); - } - - if (!user.Identity.IsAuthenticated) - { - return false; - } - - if (user.IsInRole(ConstantRoles.Admin)) - { - // Admin users have access to all of the pages. - return true; - } - - // 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 => claim.Type == ConstantPolicies.DynamicPermissionClaimType && - claim.Value == currentClaimValue); + // Admin users have access to all of the pages. + return true; } + + // 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)); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/SiteStatService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/SiteStatService.cs index 5bd9b50..a8f7da8 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/SiteStatService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/SiteStatService.cs @@ -1,85 +1,80 @@ -using ASPNETCoreIdentitySample.DataLayer.Context; +using System.Security.Claims; +using ASPNETCoreIdentitySample.DataLayer.Context; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; -using Microsoft.EntityFrameworkCore; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using System; using DNTPersianUtils.Core; +using Microsoft.EntityFrameworkCore; + +namespace ASPNETCoreIdentitySample.Services.Identity; -namespace ASPNETCoreIdentitySample.Services.Identity +public class SiteStatService : ISiteStatService { - public class SiteStatService : ISiteStatService + private readonly IUnitOfWork _uow; + private readonly IApplicationUserManager _userManager; + private readonly DbSet _users; + + public SiteStatService( + IApplicationUserManager userManager, + IUnitOfWork uow) { - private readonly IUnitOfWork _uow; - private readonly IApplicationUserManager _userManager; - private readonly DbSet _users; + _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + _uow = uow ?? throw new ArgumentNullException(nameof(uow)); + _users = uow.Set(); + } - public SiteStatService( - IApplicationUserManager userManager, - IUnitOfWork uow) - { - _userManager = userManager ?? throw new ArgumentNullException(nameof(_userManager)); - _uow = uow ?? throw new ArgumentNullException(nameof(_uow)); - _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(); + } - 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(); - } + 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(); + } - 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(); - } + public async Task GetUsersAverageAge() + { + var users = await _users.AsNoTracking() + .Where(x => x.BirthDate != null && x.IsActive) + .OrderBy(x => x.BirthDate) + .ToListAsync(); - public async Task GetUsersAverageAge() + var count = users.Count; + if (count == 0) { - var users = await _users.AsNoTracking() - .Where(x => x.BirthDate != null && x.IsActive) - .OrderBy(x => x.BirthDate) - .ToListAsync(); - - var count = users.Count; - if (count == 0) - { - return new AgeStatViewModel(); - } - - var sum = users.Where(user => user.BirthDate != null).Sum(user => (int?)user.BirthDate.Value.GetAge()) ?? 0; - - return new AgeStatViewModel - { - AverageAge = sum / count, - MaxAgeUser = users.First(), - MinAgeUser = users.Last(), - UsersCount = count - }; + return new AgeStatViewModel(); } - public async Task UpdateUserLastVisitDateTimeAsync(ClaimsPrincipal claimsPrincipal) + var sum = users.Where(user => user.BirthDate != null).Sum(user => (int?)user.BirthDate.Value.GetAge()) ?? 0; + + return new AgeStatViewModel { - var user = await _userManager.GetUserAsync(claimsPrincipal); - user.LastVisitDateTime = DateTime.UtcNow; - await _userManager.UpdateAsync(user); - } + AverageAge = sum / count, + MaxAgeUser = users.First(), + MinAgeUser = users.Last(), + UsersCount = count + }; + } + + public async Task UpdateUserLastVisitDateTimeAsync(ClaimsPrincipal claimsPrincipal) + { + var user = await _userManager.GetUserAsync(claimsPrincipal); + user.LastVisitDateTime = DateTime.UtcNow; + await _userManager.UpdateAsync(user); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/UsedPasswordsService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/UsedPasswordsService.cs index 839b51f..27845d1 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/UsedPasswordsService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/UsedPasswordsService.cs @@ -2,98 +2,109 @@ using ASPNETCoreIdentitySample.Entities.AuditableEntity; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; -using System.Linq; -using System.Threading.Tasks; -using System; -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -namespace ASPNETCoreIdentitySample.Services.Identity +namespace ASPNETCoreIdentitySample.Services.Identity; + +public class UsedPasswordsService : IUsedPasswordsService { - public class UsedPasswordsService : IUsedPasswordsService + private readonly int _changePasswordReminderDays; + private readonly int _notAllowedPreviouslyUsedPasswords; + private readonly IPasswordHasher _passwordHasher; + private readonly IUnitOfWork _uow; + private readonly DbSet _userUsedPasswords; + + public UsedPasswordsService( + IUnitOfWork uow, + IPasswordHasher passwordHasher, + IOptionsSnapshot configurationRoot) { - private readonly int _changePasswordReminderDays; - private readonly int _notAllowedPreviouslyUsedPasswords; - private readonly IPasswordHasher _passwordHasher; - private readonly IUnitOfWork _uow; - private readonly DbSet _userUsedPasswords; + _uow = uow ?? throw new ArgumentNullException(nameof(uow)); - public UsedPasswordsService( - IUnitOfWork uow, - IPasswordHasher passwordHasher, - IOptionsSnapshot configurationRoot) + _userUsedPasswords = _uow.Set(); + _passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); + if (configurationRoot == null) { - _uow = uow ?? throw new ArgumentNullException(nameof(uow)); + throw new ArgumentNullException(nameof(configurationRoot)); + } - _userUsedPasswords = _uow.Set() ?? throw new ArgumentNullException(nameof(_userUsedPasswords)); - _passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); - if (configurationRoot == null) throw new ArgumentNullException(nameof(configurationRoot)); - var configurationRootValue = configurationRoot.Value; - if (configurationRootValue == null) throw new ArgumentNullException(nameof(configurationRootValue)); - _notAllowedPreviouslyUsedPasswords = configurationRootValue.NotAllowedPreviouslyUsedPasswords; - _changePasswordReminderDays = configurationRootValue.ChangePasswordReminderDays; + var configurationRootValue = configurationRoot.Value; + if (configurationRootValue == null) + { + throw new InvalidOperationException($"{nameof(configurationRootValue)} is null"); } - public async Task AddToUsedPasswordsListAsync(User user) + _notAllowedPreviouslyUsedPasswords = configurationRootValue.NotAllowedPreviouslyUsedPasswords; + _changePasswordReminderDays = configurationRootValue.ChangePasswordReminderDays; + } + + public async Task AddToUsedPasswordsListAsync(User user) + { + if (user == null) { - await _userUsedPasswords.AddAsync(new UserUsedPassword - { - UserId = user.Id, - HashedPassword = user.PasswordHash - }); - await _uow.SaveChangesAsync(); + throw new ArgumentNullException(nameof(user)); } - public async Task GetLastUserPasswordChangeDateAsync(int userId) + await _userUsedPasswords.AddAsync(new UserUsedPassword { - var lastPasswordHistory = - await _userUsedPasswords//.AsNoTracking() --> removes shadow properties - .OrderByDescending(userUsedPassword => userUsedPassword.Id) - .FirstOrDefaultAsync(userUsedPassword => userUsedPassword.UserId == userId); - if (lastPasswordHistory == null) - { - return null; - } + UserId = user.Id, + HashedPassword = user.PasswordHash + }); + await _uow.SaveChangesAsync(); + } - var createdDateValue = _uow.GetShadowPropertyValue(lastPasswordHistory, AuditableShadowProperties.CreatedDateTime); - return createdDateValue == null ? - (DateTime?)null : - DateTime.SpecifyKind((DateTime)createdDateValue, DateTimeKind.Utc); + public async Task GetLastUserPasswordChangeDateAsync(int userId) + { + var lastPasswordHistory = + await _userUsedPasswords //.AsNoTracking() --> removes shadow properties + .OrderByDescending(userUsedPassword => userUsedPassword.Id) + .FirstOrDefaultAsync(userUsedPassword => userUsedPassword.UserId == userId); + if (lastPasswordHistory == null) + { + return null; } - public async Task IsLastUserPasswordTooOldAsync(int userId) + var createdDateValue = + _uow.GetShadowPropertyValue(lastPasswordHistory, AuditableShadowProperties.CreatedDateTime); + return createdDateValue == null ? null : DateTime.SpecifyKind((DateTime)createdDateValue, DateTimeKind.Utc); + } + + public async Task IsLastUserPasswordTooOldAsync(int userId) + { + var createdDateTime = await GetLastUserPasswordChangeDateAsync(userId); + if (createdDateTime == null) { - var createdDateTime = await GetLastUserPasswordChangeDateAsync(userId); - if (createdDateTime == null) - { - return false; - } - return createdDateTime.Value.AddDays(_changePasswordReminderDays) < DateTime.UtcNow; + return false; } - /// - /// This method will be used by CustomPasswordValidator automatically, - /// every time a user wants to change his/her info. - /// - public async Task IsPreviouslyUsedPasswordAsync(User user, string newPassword) - { - if (user.Id == 0) - { - // A new user wants to register at our site - return false; - } + return createdDateTime.Value.AddDays(_changePasswordReminderDays) < DateTime.UtcNow; + } - var userId = user.Id; - var usedPasswords = await _userUsedPasswords - .AsNoTracking() - .Where(userUsedPassword => userUsedPassword.UserId == userId) - .OrderByDescending(userUsedPassword => userUsedPassword.Id) - .Select(userUsedPassword => userUsedPassword.HashedPassword) - .Take(_notAllowedPreviouslyUsedPasswords) - .ToListAsync(); - return usedPasswords.Any(hashedPassword => _passwordHasher.VerifyHashedPassword(user, hashedPassword, newPassword) != PasswordVerificationResult.Failed); + /// + /// This method will be used by CustomPasswordValidator automatically, + /// every time a user wants to change his/her info. + /// + public async Task IsPreviouslyUsedPasswordAsync(User user, string newPassword) + { + if (user is null || user.Id == 0) + { + // A new user wants to register at our site + return false; } + + var userId = user.Id; + var usedPasswords = await _userUsedPasswords + .AsNoTracking() + .Where(userUsedPassword => userUsedPassword.UserId == userId) + .OrderByDescending(userUsedPassword => userUsedPassword.Id) + .Select(userUsedPassword => userUsedPassword.HashedPassword) + .Take(_notAllowedPreviouslyUsedPasswords) + .ToListAsync(); + return usedPasswords.Any(hashedPassword => + _passwordHasher.VerifyHashedPassword(user, hashedPassword, newPassword) != + PasswordVerificationResult.Failed); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.Services/Identity/UsersPhotoService.cs b/src/ASPNETCoreIdentitySample.Services/Identity/UsersPhotoService.cs index 2780562..27a02c6 100644 --- a/src/ASPNETCoreIdentitySample.Services/Identity/UsersPhotoService.cs +++ b/src/ASPNETCoreIdentitySample.Services/Identity/UsersPhotoService.cs @@ -1,77 +1,77 @@ -using System.IO; -using ASPNETCoreIdentitySample.Entities.Identity; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; +using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using Microsoft.Extensions.Options; -using System; using DNTCommon.Web.Core; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace ASPNETCoreIdentitySample.Services.Identity; -namespace ASPNETCoreIdentitySample.Services.Identity +public class UsersPhotoService : IUsersPhotoService { - public class UsersPhotoService : IUsersPhotoService + private readonly IHttpContextAccessor _contextAccessor; + private readonly IWebHostEnvironment _hostingEnvironment; + private readonly IOptionsSnapshot _siteSettings; + + public UsersPhotoService( + IHttpContextAccessor contextAccessor, + IWebHostEnvironment hostingEnvironment, + IOptionsSnapshot siteSettings) { - private readonly IHttpContextAccessor _contextAccessor; - private readonly IWebHostEnvironment _hostingEnvironment; - private readonly IOptionsSnapshot _siteSettings; + _contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + _siteSettings = siteSettings ?? throw new ArgumentNullException(nameof(siteSettings)); + } - public UsersPhotoService( - IHttpContextAccessor contextAccessor, - IWebHostEnvironment hostingEnvironment, - IOptionsSnapshot siteSettings) + public string GetUsersAvatarsFolderPath() + { + var usersAvatarsFolder = _siteSettings.Value.UsersAvatarsFolder; + var uploadsRootFolder = Path.Combine(_hostingEnvironment.WebRootPath, usersAvatarsFolder); + if (!Directory.Exists(uploadsRootFolder)) { - _contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); - _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); - _siteSettings = siteSettings ?? throw new ArgumentNullException(nameof(siteSettings)); + Directory.CreateDirectory(uploadsRootFolder); } - public string GetUsersAvatarsFolderPath() - { - var usersAvatarsFolder = _siteSettings.Value.UsersAvatarsFolder; - var uploadsRootFolder = Path.Combine(_hostingEnvironment.WebRootPath, usersAvatarsFolder); - if (!Directory.Exists(uploadsRootFolder)) - { - Directory.CreateDirectory(uploadsRootFolder); - } - return uploadsRootFolder; - } + return uploadsRootFolder; + } - public void SetUserDefaultPhoto(User user) + public void SetUserDefaultPhoto(User user) + { + if (user is null || !string.IsNullOrWhiteSpace(user.PhotoFileName)) { - if (!string.IsNullOrWhiteSpace(user.PhotoFileName)) - { - return; - } - - var avatarPath = Path.Combine(GetUsersAvatarsFolderPath(), user.PhotoFileName ?? string.Empty); - if (!File.Exists(avatarPath)) - { - user.PhotoFileName = _siteSettings.Value.UserDefaultPhoto; - } + return; } - public string GetUserDefaultPhoto(string photoFileName) + var avatarPath = Path.Combine(GetUsersAvatarsFolderPath(), user.PhotoFileName ?? string.Empty); + if (!File.Exists(avatarPath)) { - if (string.IsNullOrWhiteSpace(photoFileName)) - { - return _siteSettings.Value.UserDefaultPhoto; - } - - var avatarPath = Path.Combine(GetUsersAvatarsFolderPath(), photoFileName ?? string.Empty); - return !File.Exists(avatarPath) ? _siteSettings.Value.UserDefaultPhoto : photoFileName; + user.PhotoFileName = _siteSettings.Value.UserDefaultPhoto; } + } - public string GetUserPhotoUrl(string photoFileName) + public string GetUserDefaultPhoto(string photoFileName) + { + if (string.IsNullOrWhiteSpace(photoFileName)) { - photoFileName = GetUserDefaultPhoto(photoFileName); - return $"~/{_siteSettings.Value.UsersAvatarsFolder}/{photoFileName}"; + return _siteSettings.Value.UserDefaultPhoto; } - public string GetCurrentUserPhotoUrl() - { - var photoFileName = _contextAccessor.HttpContext.User.Identity.GetUserClaimValue(ApplicationClaimsPrincipalFactory.PhotoFileName); - return GetUserPhotoUrl(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); + 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 1c1f475..a71a7f9 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/ASPNETCoreIdentitySample.ViewModels.csproj +++ b/src/ASPNETCoreIdentitySample.ViewModels/ASPNETCoreIdentitySample.ViewModels.csproj @@ -1,16 +1,15 @@  - net5.0 - RCS1090 + net6.0 - - - + + + diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/AgeStatViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/AgeStatViewModel.cs index 2202cdd..53b4b67 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/AgeStatViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/AgeStatViewModel.cs @@ -1,17 +1,18 @@ using ASPNETCoreIdentitySample.Entities.Identity; using DNTPersianUtils.Core; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class AgeStatViewModel { - public class AgeStatViewModel - { - public const char RleChar = (char)0x202B; + public const char RleChar = (char)0x202B; - public int UsersCount { set; get; } - public int AverageAge { set; get; } - public User MaxAgeUser { set; get; } - public User MinAgeUser { set; get; } + public int UsersCount { set; get; } + public int AverageAge { set; get; } + public User MaxAgeUser { set; get; } + public User MinAgeUser { set; get; } - public string MinMax => $"{RleChar}جوان‌ترین عضو: {MinAgeUser.DisplayName} ({MinAgeUser.BirthDate.Value.GetAge()})، مسن‌ترین عضو: {MaxAgeUser.DisplayName} ({MaxAgeUser.BirthDate.Value.GetAge()})، در بین {UsersCount} نفر"; - } + public string MinMax => + Invariant( + $"{RleChar}جوان‌ترین عضو: {MinAgeUser.DisplayName} ({MinAgeUser.BirthDate.Value.GetAge()})، مسن‌ترین عضو: {MaxAgeUser.DisplayName} ({MaxAgeUser.BirthDate.Value.GetAge()})، در بین {UsersCount} نفر"); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ChangePasswordViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ChangePasswordViewModel.cs index f14b0d1..cc1e11c 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ChangePasswordViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ChangePasswordViewModel.cs @@ -1,30 +1,27 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class ChangePasswordViewModel { - public class ChangePasswordViewModel - { - [Required(ErrorMessage = "(*)")] - [DataType(DataType.Password)] - [Display(Name = "کلمه‌ی عبور فعلی")] - public string OldPassword { get; set; } + [Required(ErrorMessage = "(*)")] + [DataType(DataType.Password)] + [Display(Name = "کلمه‌ی عبور فعلی")] + public string OldPassword { get; set; } - [Required(ErrorMessage = "(*)")] - [StringLength(100, ErrorMessage = "{0} باید حداقل {2} و حداکثر {1} حرف باشند.", MinimumLength = 6)] - [Remote("ValidatePassword", "ChangePassword", - AdditionalFields = ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] - [DataType(DataType.Password)] - [Display(Name = "کلمه‌ی عبور جدید")] - public string NewPassword { get; set; } + [Required(ErrorMessage = "(*)")] + [StringLength(100, ErrorMessage = "{0} باید حداقل {2} و حداکثر {1} حرف باشند.", MinimumLength = 6)] + [Remote("ValidatePassword", "ChangePassword", + AdditionalFields = ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] + [DataType(DataType.Password)] + [Display(Name = "کلمه‌ی عبور جدید")] + public string NewPassword { get; set; } - [Required(ErrorMessage = "(*)")] - [DataType(DataType.Password)] - [Display(Name = "تکرار کلمه‌ی عبور جدید")] - [Compare(nameof(NewPassword), ErrorMessage = "کلمات عبور وارد شده با هم تطابق ندارند")] - public string ConfirmPassword { get; set; } + [Required(ErrorMessage = "(*)")] + [DataType(DataType.Password)] + [Display(Name = "تکرار کلمه‌ی عبور جدید")] + [Compare(nameof(NewPassword), ErrorMessage = "کلمات عبور وارد شده با هم تطابق ندارند")] + public string ConfirmPassword { get; set; } - public DateTime? LastUserPasswordChangeDate { get; set; } - } + public DateTime? LastUserPasswordChangeDate { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ChangeUserPasswordViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ChangeUserPasswordViewModel.cs index f0a3cb2..91b8b6d 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ChangeUserPasswordViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ChangeUserPasswordViewModel.cs @@ -1,27 +1,25 @@ -using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class ChangeUserPasswordViewModel { - public class ChangeUserPasswordViewModel - { - [Required(ErrorMessage = "(*)")] - [StringLength(100, ErrorMessage = "{0} باید حداقل {2} و حداکثر {1} حرف باشند.", MinimumLength = 6)] - [Remote("ValidatePassword", "ChangeUserPassword", - AdditionalFields = ViewModelConstants.AntiForgeryToken + "," + nameof(UserId), HttpMethod = "POST")] - [DataType(DataType.Password)] - [Display(Name = "کلمه‌ی عبور جدید")] - public string NewPassword { get; set; } + [Required(ErrorMessage = "(*)")] + [StringLength(100, ErrorMessage = "{0} باید حداقل {2} و حداکثر {1} حرف باشند.", MinimumLength = 6)] + [Remote("ValidatePassword", "ChangeUserPassword", + AdditionalFields = ViewModelConstants.AntiForgeryToken + "," + nameof(UserId), HttpMethod = "POST")] + [DataType(DataType.Password)] + [Display(Name = "کلمه‌ی عبور جدید")] + public string NewPassword { get; set; } - [Required(ErrorMessage = "(*)")] - [DataType(DataType.Password)] - [Display(Name = "تکرار کلمه‌ی عبور جدید")] - [Compare(nameof(NewPassword), ErrorMessage = "کلمات عبور وارد شده با هم تطابق ندارند")] - public string ConfirmPassword { get; set; } + [Required(ErrorMessage = "(*)")] + [DataType(DataType.Password)] + [Display(Name = "تکرار کلمه‌ی عبور جدید")] + [Compare(nameof(NewPassword), ErrorMessage = "کلمات عبور وارد شده با هم تطابق ندارند")] + public string ConfirmPassword { get; set; } - [HiddenInput] - public int UserId { get; set; } + [HiddenInput] + public int UserId { get; set; } - public string Name { get; set; } - } + public string Name { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/DynamicRoleClaimsManagerViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/DynamicRoleClaimsManagerViewModel.cs index dd81554..53ec70c 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/DynamicRoleClaimsManagerViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/DynamicRoleClaimsManagerViewModel.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.Entities.Identity; using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class DynamicRoleClaimsManagerViewModel { - public class DynamicRoleClaimsManagerViewModel - { - public string[] ActionIds { set; get; } + public string[] ActionIds { set; get; } - public int RoleId { set; get; } + public int RoleId { set; get; } - public Role RoleIncludeRoleClaims { set; get; } + public Role RoleIncludeRoleClaims { set; get; } - public ICollection SecuredControllerActions { set; get; } - } + public ICollection SecuredControllerActions { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/ChangePasswordNotificationViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/ChangePasswordNotificationViewModel.cs index 1a3388c..c556eab 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/ChangePasswordNotificationViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/ChangePasswordNotificationViewModel.cs @@ -1,9 +1,8 @@ using ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails; + +public class ChangePasswordNotificationViewModel : EmailsBase { - public class ChangePasswordNotificationViewModel : EmailsBase - { - public User User { set; get; } - } + public User User { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/EmailsBase.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/EmailsBase.cs index f490be0..769a702 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/EmailsBase.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/EmailsBase.cs @@ -1,8 +1,7 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails; + +public abstract class EmailsBase { - public abstract class EmailsBase - { - public string EmailSignature { set; get; } - public string MessageDateTime { set; get; } - } + public string EmailSignature { set; get; } + public string MessageDateTime { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/PasswordResetViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/PasswordResetViewModel.cs index 439e513..3da2ebe 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/PasswordResetViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/PasswordResetViewModel.cs @@ -1,8 +1,7 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails; + +public class PasswordResetViewModel : EmailsBase { - public class PasswordResetViewModel : EmailsBase - { - public int UserId { set; get; } - public string Token { set; get; } - } + public int UserId { set; get; } + public string Token { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/RegisterEmailConfirmationViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/RegisterEmailConfirmationViewModel.cs index 8e7aae2..8d4de52 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/RegisterEmailConfirmationViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/RegisterEmailConfirmationViewModel.cs @@ -1,10 +1,9 @@ using ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails; + +public class RegisterEmailConfirmationViewModel : EmailsBase { - public class RegisterEmailConfirmationViewModel : EmailsBase - { - public User User { set; get; } - public string EmailConfirmationToken { set; get; } - } -} + public User User { set; get; } + public string EmailConfirmationToken { set; get; } +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/TwoFactorSendCodeViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/TwoFactorSendCodeViewModel.cs index 0d073e7..3ed3111 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/TwoFactorSendCodeViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/TwoFactorSendCodeViewModel.cs @@ -1,7 +1,6 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails; + +public class TwoFactorSendCodeViewModel : EmailsBase { - public class TwoFactorSendCodeViewModel : EmailsBase - { - public string Token { set; get; } - } + public string Token { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/UserProfileUpdateNotificationViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/UserProfileUpdateNotificationViewModel.cs index 12fe6f4..44225de 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/UserProfileUpdateNotificationViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Emails/UserProfileUpdateNotificationViewModel.cs @@ -1,9 +1,8 @@ using ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Emails; + +public class UserProfileUpdateNotificationViewModel : EmailsBase { - public class UserProfileUpdateNotificationViewModel : EmailsBase - { - public User User { set; get; } - } + public User User { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ForgotPasswordViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ForgotPasswordViewModel.cs index f5644cc..11e175e 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ForgotPasswordViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ForgotPasswordViewModel.cs @@ -1,12 +1,9 @@ -using System.ComponentModel.DataAnnotations; +namespace ASPNETCoreIdentitySample.ViewModels.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +public class ForgotPasswordViewModel { - public class ForgotPasswordViewModel - { - [Required(ErrorMessage = "(*)")] - [EmailAddress(ErrorMessage = "لطفا آدرس ایمیل معتبری را وارد نمائید.")] - [Display(Name = "ایمیل")] - public string Email { get; set; } - } + [Required(ErrorMessage = "(*)")] + [EmailAddress(ErrorMessage = "لطفا آدرس ایمیل معتبری را وارد نمائید.")] + [Display(Name = "ایمیل")] + public string Email { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/LoginViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/LoginViewModel.cs index 47baf1b..89882c7 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/LoginViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/LoginViewModel.cs @@ -1,19 +1,16 @@ -using System.ComponentModel.DataAnnotations; +namespace ASPNETCoreIdentitySample.ViewModels.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +public class LoginViewModel { - public class LoginViewModel - { - [Required(ErrorMessage = "(*)")] - [Display(Name = "نام کاربری")] - public string Username { get; set; } + [Required(ErrorMessage = "(*)")] + [Display(Name = "نام کاربری")] + public string Username { get; set; } - [Required(ErrorMessage = "(*)")] - [Display(Name = "کلمه‌ی عبور")] - [DataType(DataType.Password)] - public string Password { get; set; } + [Required(ErrorMessage = "(*)")] + [Display(Name = "کلمه‌ی عبور")] + [DataType(DataType.Password)] + public string Password { get; set; } - [Display(Name = "به‌خاطر سپاری کلمه‌ی عبور؟")] - public bool RememberMe { get; set; } - } + [Display(Name = "به‌خاطر سپاری کلمه‌ی عبور؟")] + public bool RememberMe { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ModelIdViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ModelIdViewModel.cs index 7cf68e8..7e26599 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ModelIdViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ModelIdViewModel.cs @@ -1,7 +1,6 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class ModelIdViewModel { - public class ModelIdViewModel - { - public int Id { set; get; } - } + public int Id { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/OnlineUsersViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/OnlineUsersViewModel.cs index 444361a..43a404a 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/OnlineUsersViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/OnlineUsersViewModel.cs @@ -1,13 +1,11 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class OnlineUsersViewModel { - public class OnlineUsersViewModel - { - public List Users { set; get; } - public int NumbersToTake { set; get; } - public int MinutesToTake { set; get; } - public bool ShowMoreItemsLink { set; get; } - } + public List Users { set; get; } + public int NumbersToTake { set; get; } + public int MinutesToTake { set; get; } + public bool ShowMoreItemsLink { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedAppLogItemsViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedAppLogItemsViewModel.cs index 755ca44..cf4adbe 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedAppLogItemsViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedAppLogItemsViewModel.cs @@ -1,20 +1,18 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.Entities.Identity; using cloudscribe.Web.Pagination; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class PagedAppLogItemsViewModel { - public class PagedAppLogItemsViewModel + public PagedAppLogItemsViewModel() { - public PagedAppLogItemsViewModel() - { - Paging = new PaginationSettings(); - } + Paging = new PaginationSettings(); + } - public string LogLevel { get; set; } = string.Empty; + public string LogLevel { get; set; } = string.Empty; - public List AppLogItems { get; set; } + public List AppLogItems { get; set; } - public PaginationSettings Paging { get; set; } - } + public PaginationSettings Paging { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedUsersListViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedUsersListViewModel.cs index e8a16d4..be637f6 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedUsersListViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/PagedUsersListViewModel.cs @@ -1,20 +1,18 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.Entities.Identity; using cloudscribe.Web.Pagination; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class PagedUsersListViewModel { - public class PagedUsersListViewModel + public PagedUsersListViewModel() { - public PagedUsersListViewModel() - { - Paging = new PaginationSettings(); - } + Paging = new PaginationSettings(); + } - public List Users { get; set; } + public List Users { get; set; } - public List Roles { get; set; } + public List Roles { get; set; } - public PaginationSettings Paging { get; set; } - } -} + public PaginationSettings Paging { get; set; } +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/RegisterViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/RegisterViewModel.cs index de82261..4631440 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/RegisterViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/RegisterViewModel.cs @@ -1,50 +1,48 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class RegisterViewModel { - public class RegisterViewModel - { - [Required(ErrorMessage = "(*)")] - [Display(Name = "نام کاربری")] - [Remote("ValidateUsername", "Register", - AdditionalFields = nameof(Email) + "," + ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] - [RegularExpression("^[a-zA-Z_]*$", ErrorMessage = "لطفا تنها از حروف انگلیسی استفاده نمائید")] - public string Username { get; set; } + [Required(ErrorMessage = "(*)")] + [Display(Name = "نام کاربری")] + [Remote("ValidateUsername", "Register", + AdditionalFields = nameof(Email) + "," + ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] + [RegularExpression("^[a-zA-Z_]*$", ErrorMessage = "لطفا تنها از حروف انگلیسی استفاده نمائید")] + public string Username { get; set; } - [Display(Name = "نام")] - [Required(ErrorMessage = "(*)")] - [StringLength(450)] - [RegularExpression(@"^[\u0600-\u06FF,\u0590-\u05FF\s]*$", - ErrorMessage = "لطفا تنها از حروف فارسی استفاده نمائید")] - public string FirstName { get; set; } + [Display(Name = "نام")] + [Required(ErrorMessage = "(*)")] + [StringLength(450)] + [RegularExpression(@"^[\u0600-\u06FF,\u0590-\u05FF\s]*$", + ErrorMessage = "لطفا تنها از حروف فارسی استفاده نمائید")] + public string FirstName { get; set; } - [Display(Name = "نام خانوادگی")] - [Required(ErrorMessage = "(*)")] - [StringLength(450)] - [RegularExpression(@"^[\u0600-\u06FF,\u0590-\u05FF\s]*$", - ErrorMessage = "لطفا تنها از حروف فارسی استفاده نمائید")] - public string LastName { get; set; } + [Display(Name = "نام خانوادگی")] + [Required(ErrorMessage = "(*)")] + [StringLength(450)] + [RegularExpression(@"^[\u0600-\u06FF,\u0590-\u05FF\s]*$", + ErrorMessage = "لطفا تنها از حروف فارسی استفاده نمائید")] + public string LastName { get; set; } - [Required(ErrorMessage = "(*)")] - [Remote("ValidateUsername", "Register", - AdditionalFields = nameof(Username) + "," + ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] - [EmailAddress(ErrorMessage = "لطفا آدرس ایمیل معتبری را وارد نمائید.")] - [Display(Name = "ایمیل")] - public string Email { get; set; } + [Required(ErrorMessage = "(*)")] + [Remote("ValidateUsername", "Register", + AdditionalFields = nameof(Username) + "," + ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] + [EmailAddress(ErrorMessage = "لطفا آدرس ایمیل معتبری را وارد نمائید.")] + [Display(Name = "ایمیل")] + public string Email { get; set; } - [Required(ErrorMessage = "(*)")] - [StringLength(100, ErrorMessage = "{0} باید حداقل {2} و حداکثر {1} حرف باشند.", MinimumLength = 6)] - [Remote("ValidatePassword", "Register", - AdditionalFields = nameof(Username) + "," + ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] - [DataType(DataType.Password)] - [Display(Name = "کلمه‌ی عبور")] - public string Password { get; set; } + [Required(ErrorMessage = "(*)")] + [StringLength(100, ErrorMessage = "{0} باید حداقل {2} و حداکثر {1} حرف باشند.", MinimumLength = 6)] + [Remote("ValidatePassword", "Register", + AdditionalFields = nameof(Username) + "," + ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] + [DataType(DataType.Password)] + [Display(Name = "کلمه‌ی عبور")] + public string Password { get; set; } - [Required(ErrorMessage = "(*)")] - [DataType(DataType.Password)] - [Display(Name = "تکرار کلمه‌ی عبور")] - [Compare(nameof(Password), ErrorMessage = "کلمات عبور وارد شده با هم تطابق ندارند")] - public string ConfirmPassword { get; set; } - } + [Required(ErrorMessage = "(*)")] + [DataType(DataType.Password)] + [Display(Name = "تکرار کلمه‌ی عبور")] + [Compare(nameof(Password), ErrorMessage = "کلمات عبور وارد شده با هم تطابق ندارند")] + public string ConfirmPassword { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ResetPasswordViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ResetPasswordViewModel.cs index 5ad4fba..c7f1b99 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ResetPasswordViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ResetPasswordViewModel.cs @@ -1,29 +1,27 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class ResetPasswordViewModel { - public class ResetPasswordViewModel - { - [Required(ErrorMessage = "(*)")] - [EmailAddress(ErrorMessage = "لطفا آدرس ایمیل معتبری را وارد نمائید.")] - [Display(Name = "ایمیل")] - public string Email { get; set; } + [Required(ErrorMessage = "(*)")] + [EmailAddress(ErrorMessage = "لطفا آدرس ایمیل معتبری را وارد نمائید.")] + [Display(Name = "ایمیل")] + public string Email { get; set; } - [Required(ErrorMessage = "(*)")] - [StringLength(100, ErrorMessage = "{0} باید حداقل {2} و حداکثر {1} حرف باشند.", MinimumLength = 6)] - [Remote("ValidatePassword", "ForgotPassword", - AdditionalFields = nameof(Email) + "," + ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] - [DataType(DataType.Password)] - [Display(Name = "کلمه‌ی عبور")] - public string Password { get; set; } + [Required(ErrorMessage = "(*)")] + [StringLength(100, ErrorMessage = "{0} باید حداقل {2} و حداکثر {1} حرف باشند.", MinimumLength = 6)] + [Remote("ValidatePassword", "ForgotPassword", + AdditionalFields = nameof(Email) + "," + ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] + [DataType(DataType.Password)] + [Display(Name = "کلمه‌ی عبور")] + public string Password { get; set; } - [Required(ErrorMessage = "(*)")] - [DataType(DataType.Password)] - [Display(Name = "تکرار کلمه‌ی عبور")] - [Compare(nameof(Password), ErrorMessage = "کلمات عبور وارد شده با هم تطابق ندارند")] - public string ConfirmPassword { get; set; } + [Required(ErrorMessage = "(*)")] + [DataType(DataType.Password)] + [Display(Name = "تکرار کلمه‌ی عبور")] + [Compare(nameof(Password), ErrorMessage = "کلمات عبور وارد شده با هم تطابق ندارند")] + public string ConfirmPassword { get; set; } - public string Code { get; set; } - } + public string Code { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/RoleAndUsersCountViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/RoleAndUsersCountViewModel.cs index 9897081..b35451a 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/RoleAndUsersCountViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/RoleAndUsersCountViewModel.cs @@ -1,10 +1,9 @@ using ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class RoleAndUsersCountViewModel { - public class RoleAndUsersCountViewModel - { - public Role Role { set; get; } - public int UsersCount { set; get; } - } + public Role Role { set; get; } + public int UsersCount { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/RoleViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/RoleViewModel.cs index f200a8d..78e0fb5 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/RoleViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/RoleViewModel.cs @@ -1,15 +1,13 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class RoleViewModel { - public class RoleViewModel - { - [HiddenInput] - public string Id { set; get; } + [HiddenInput] + public string Id { set; get; } - [Required(ErrorMessage = "(*)")] - [Display(Name = "نام نقش")] - public string Name { set; get; } - } + [Required(ErrorMessage = "(*)")] + [Display(Name = "نام نقش")] + public string Name { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/SearchUsersViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/SearchUsersViewModel.cs index b2b264c..4f1486c 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/SearchUsersViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/SearchUsersViewModel.cs @@ -1,56 +1,53 @@ -using System.ComponentModel.DataAnnotations; +namespace ASPNETCoreIdentitySample.ViewModels.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +public class SearchUsersViewModel { - 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; } + [Display(Name = "قسمتی از نام خانوادگی")] + public bool IsPartOfLastName { set; get; } - [Display(Name = "قسمتی از نام کاربری")] - public bool IsPartOfUserName { set; get; } + [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 HasEmailConfirmed { set; get; } - [Display(Name = "فقط فعال‌ها")] - public bool UserIsActive { set; get; } + [Display(Name = "فقط فعال‌ها")] + public bool UserIsActive { set; get; } - [Display(Name = "کاربران فعال و غیرفعال")] - public bool ShowAllUsers { set; get; } + [Display(Name = "کاربران فعال و غیرفعال")] + public bool ShowAllUsers { set; get; } - [Display(Name = "دارای حساب کاربری قفل شده")] - public bool UserIsLockedOut { set; get; } + [Display(Name = "دارای حساب کاربری قفل شده")] + public bool UserIsLockedOut { set; get; } - [Display(Name = "دارای اعتبارسنجی دو مرحله‌ای")] - public bool HasTwoFactorEnabled { set; get; } + [Display(Name = "دارای اعتبارسنجی دو مرحله‌ای")] + public bool HasTwoFactorEnabled { set; get; } - [Display(Name = "تعداد ردیف بازگشتی")] - [Required(ErrorMessage = "(*)")] - [Range(1, 1000, ErrorMessage = "عدد وارد شده باید در بازه 1 تا 1000 تعیین شود")] - public int MaxNumberOfRows { set; get; } + [Display(Name = "تعداد ردیف بازگشتی")] + [Required(ErrorMessage = "(*)")] + [Range(1, 1000, ErrorMessage = "عدد وارد شده باید در بازه 1 تا 1000 تعیین شود")] + public int MaxNumberOfRows { set; get; } - public PagedUsersListViewModel PagedUsersList { set; get; } + public PagedUsersListViewModel PagedUsersList { set; get; } - public SearchUsersViewModel() - { - ShowAllUsers = true; - MaxNumberOfRows = 7; - } + public SearchUsersViewModel() + { + ShowAllUsers = true; + MaxNumberOfRows = 7; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/ActiveDatabase.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/ActiveDatabase.cs index 6b880c6..ff5f3b5 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/ActiveDatabase.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/ActiveDatabase.cs @@ -1,10 +1,9 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public enum ActiveDatabase { - public enum ActiveDatabase - { - LocalDb, - SqlServer, - InMemoryDatabase, - SQLite - } + LocalDb, + SqlServer, + InMemoryDatabase, + SQLite } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/AdminUserSeed.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/AdminUserSeed.cs index 142237a..7b7a6ae 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/AdminUserSeed.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/AdminUserSeed.cs @@ -1,10 +1,9 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class AdminUserSeed { - public class AdminUserSeed - { - public string Username { get; set; } - public string Password { get; set; } - public string Email { get; set; } - public string RoleName { get; set; } - } + public string Username { get; set; } + public string Password { get; set; } + public string Email { get; set; } + public string RoleName { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Connectionstrings.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Connectionstrings.cs index 409a3b5..8f232cf 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Connectionstrings.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Connectionstrings.cs @@ -1,9 +1,8 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class Connectionstrings { - public class Connectionstrings - { - public SqlServer SqlServer { get; set; } - public Localdb LocalDb { get; set; } - public SQLite SQLite { get; set; } - } + public SqlServer SqlServer { get; set; } + public Localdb LocalDb { get; set; } + public SQLite SQLite { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/CookieOptions.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/CookieOptions.cs index 0d2a234..2a2ff28 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/CookieOptions.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/CookieOptions.cs @@ -1,17 +1,14 @@ -using System; +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +public class CookieOptions { - public class CookieOptions - { - public string AccessDeniedPath { get; set; } - public string CookieName { get; set; } - public TimeSpan ExpireTimeSpan { get; set; } - public string LoginPath { get; set; } - public string LogoutPath { get; set; } - public bool SlidingExpiration { get; set; } - public bool UseDistributedCacheTicketStore { set; get; } - public DistributedSqlServerCacheOptions DistributedSqlServerCacheOptions { set; get; } - public DistributedSqliteCacheOptions DistributedSqliteCacheOptions { set; get; } - } + public string AccessDeniedPath { get; set; } + public string CookieName { get; set; } + public TimeSpan ExpireTimeSpan { get; set; } + public string LoginPath { get; set; } + public string LogoutPath { get; set; } + public bool SlidingExpiration { get; set; } + public bool UseDistributedCacheTicketStore { set; get; } + public DistributedSqlServerCacheOptions DistributedSqlServerCacheOptions { set; get; } + public DistributedSqliteCacheOptions DistributedSqliteCacheOptions { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DataProtectionOptions.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DataProtectionOptions.cs index 2bcb0a5..f73b3c4 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DataProtectionOptions.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DataProtectionOptions.cs @@ -1,10 +1,7 @@ -using System; +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +public class DataProtectionOptions { - public class DataProtectionOptions - { - public TimeSpan DataProtectionKeyLifetime { get; set; } - public string ApplicationName { get; set; } - } + public TimeSpan DataProtectionKeyLifetime { get; set; } + public string ApplicationName { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DataProtectionX509Certificate.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DataProtectionX509Certificate.cs index a032611..5496a22 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DataProtectionX509Certificate.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DataProtectionX509Certificate.cs @@ -1,8 +1,7 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class DataProtectionX509Certificate { - public class DataProtectionX509Certificate - { - public string FileName { set; get; } - public string Password { set; get; } - } + public string FileName { set; get; } + public string Password { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DistributedSqlServerCacheOptions.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DistributedSqlServerCacheOptions.cs index d210fc5..f1ad3d2 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DistributedSqlServerCacheOptions.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DistributedSqlServerCacheOptions.cs @@ -1,9 +1,8 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class DistributedSqlServerCacheOptions { - public class DistributedSqlServerCacheOptions - { - public string ConnectionString { set; get; } - public string TableName { set; get; } - public string SchemaName { set; get; } - } + public string ConnectionString { set; get; } + public string TableName { set; get; } + public string SchemaName { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DistributedSqliteCacheOptions.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DistributedSqliteCacheOptions.cs index b42d7de..06c01a0 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DistributedSqliteCacheOptions.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/DistributedSqliteCacheOptions.cs @@ -1,8 +1,6 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings -{ +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; - public class DistributedSqliteCacheOptions - { - public string ConnectionString { set; get; } - } +public class DistributedSqliteCacheOptions +{ + public string ConnectionString { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Localdb.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Localdb.cs index f8d57f2..688bdee 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Localdb.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Localdb.cs @@ -1,8 +1,7 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class Localdb { - public class Localdb - { - public string InitialCatalog { get; set; } - public string AttachDbFilename { get; set; } - } + public string InitialCatalog { get; set; } + public string AttachDbFilename { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Logging.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Logging.cs index 708d8e4..0dd0eaf 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Logging.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Logging.cs @@ -1,8 +1,7 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class Logging { - public class Logging - { - public bool IncludeScopes { get; set; } - public Loglevel LogLevel { get; set; } - } + public bool IncludeScopes { get; set; } + public Loglevel LogLevel { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Loglevel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Loglevel.cs index e781818..5e38b74 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Loglevel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/Loglevel.cs @@ -1,11 +1,10 @@ using MsLogLevel = Microsoft.Extensions.Logging.LogLevel; -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class Loglevel { - public class Loglevel - { - public MsLogLevel Default { get; set; } - public MsLogLevel System { get; set; } - public MsLogLevel Microsoft { get; set; } - } + public MsLogLevel Default { get; set; } + public MsLogLevel System { get; set; } + public MsLogLevel Microsoft { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SQLite.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SQLite.cs index eea83d6..79b7327 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SQLite.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SQLite.cs @@ -1,8 +1,6 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings -{ +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; - public class SQLite - { - public string ApplicationDbContextConnection { get; set; } - } +public class SQLite +{ + public string ApplicationDbContextConnection { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SiteSettings.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SiteSettings.cs index 5964f1b..469d7dd 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SiteSettings.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SiteSettings.cs @@ -1,30 +1,28 @@ -using System; -using DNTCommon.Web.Core; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class SiteSettings { - public class SiteSettings - { - public AdminUserSeed AdminUserSeed { get; set; } - public Logging Logging { get; set; } - public SmtpConfig Smtp { get; set; } - public Connectionstrings ConnectionStrings { get; set; } - public bool EnableEmailConfirmation { get; set; } - public TimeSpan EmailConfirmationTokenProviderLifespan { get; set; } - public int NotAllowedPreviouslyUsedPasswords { get; set; } - public int ChangePasswordReminderDays { get; set; } - public PasswordOptions PasswordOptions { get; set; } - public ActiveDatabase ActiveDatabase { get; set; } - public string UsersAvatarsFolder { get; set; } - public string UserDefaultPhoto { get; set; } - public string ContentSecurityPolicyErrorLogUri { get; set; } - public CookieOptions CookieOptions { get; set; } - public DataProtectionOptions DataProtectionOptions { get; set; } - public LockoutOptions LockoutOptions { get; set; } - public UserAvatarImageOptions UserAvatarImageOptions { get; set; } - public string[] EmailsBanList { get; set; } - public string[] PasswordsBanList { get; set; } - public DataProtectionX509Certificate DataProtectionX509Certificate { get; set; } - } + public AdminUserSeed AdminUserSeed { get; set; } + public Logging Logging { get; set; } + public SmtpConfig Smtp { get; set; } + public Connectionstrings ConnectionStrings { get; set; } + public bool EnableEmailConfirmation { get; set; } + public TimeSpan EmailConfirmationTokenProviderLifespan { get; set; } + public int NotAllowedPreviouslyUsedPasswords { get; set; } + public int ChangePasswordReminderDays { get; set; } + public PasswordOptions PasswordOptions { get; set; } + public ActiveDatabase ActiveDatabase { get; set; } + public string UsersAvatarsFolder { get; set; } + public string UserDefaultPhoto { get; set; } + public string ContentSecurityPolicyErrorLogUri { get; set; } + public CookieOptions CookieOptions { get; set; } + public DataProtectionOptions DataProtectionOptions { get; set; } + public LockoutOptions LockoutOptions { get; set; } + public UserAvatarImageOptions UserAvatarImageOptions { get; set; } + public string[] EmailsBanList { get; set; } + public string[] PasswordsBanList { get; set; } + public DataProtectionX509Certificate DataProtectionX509Certificate { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SqlServer.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SqlServer.cs index cbb988d..f756c4a 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SqlServer.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/SqlServer.cs @@ -1,7 +1,6 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class SqlServer { - public class SqlServer - { - public string ApplicationDbContextConnection { get; set; } - } + public string ApplicationDbContextConnection { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/UserAvatarImageOptions.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/UserAvatarImageOptions.cs index 704e31e..55b5267 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/UserAvatarImageOptions.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/Settings/UserAvatarImageOptions.cs @@ -1,8 +1,7 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings +namespace ASPNETCoreIdentitySample.ViewModels.Identity.Settings; + +public class UserAvatarImageOptions { - public class UserAvatarImageOptions - { - public int MaxWidth { set; get; } - public int MaxHeight { set; get; } - } + public int MaxWidth { set; get; } + public int MaxHeight { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/SortOrder.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/SortOrder.cs index 731f72f..d77293f 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/SortOrder.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/SortOrder.cs @@ -1,9 +1,8 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public enum SortOrder { - public enum SortOrder - { - Unspecified = -1, - Ascending = 0, - Descending = 1 - } + Unspecified = -1, + Ascending = 0, + Descending = 1 } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/TodayBirthDaysViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/TodayBirthDaysViewModel.cs index b209fa2..13423f3 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/TodayBirthDaysViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/TodayBirthDaysViewModel.cs @@ -1,12 +1,10 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class TodayBirthDaysViewModel { - public class TodayBirthDaysViewModel - { - public List Users { set; get; } + public List Users { set; get; } - public AgeStatViewModel AgeStat { set; get; } - } + public AgeStatViewModel AgeStat { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserCardItemActiveTab.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserCardItemActiveTab.cs new file mode 100644 index 0000000..41244c8 --- /dev/null +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserCardItemActiveTab.cs @@ -0,0 +1,7 @@ +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public enum UserCardItemActiveTab +{ + UserInfo, + UserAdmin +} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserCardItemViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserCardItemViewModel.cs index 973c028..54dd3aa 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserCardItemViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserCardItemViewModel.cs @@ -1,19 +1,11 @@ -using System.Collections.Generic; -using ASPNETCoreIdentitySample.Entities.Identity; +using ASPNETCoreIdentitySample.Entities.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity -{ - public enum UserCardItemActiveTab - { - UserInfo, - UserAdmin - } +namespace ASPNETCoreIdentitySample.ViewModels.Identity; - public class UserCardItemViewModel - { - public User User { set; get; } - public bool ShowAdminParts { set; get; } - public List Roles { get; set; } - public UserCardItemActiveTab ActiveTab { get; set; } - } +public class UserCardItemViewModel +{ + public User User { set; get; } + public bool ShowAdminParts { set; get; } + public List Roles { get; set; } + public UserCardItemActiveTab ActiveTab { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserProfileViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserProfileViewModel.cs index eca5fdf..28a355a 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserProfileViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/UserProfileViewModel.cs @@ -1,67 +1,65 @@ -using System.ComponentModel.DataAnnotations; -using DNTCommon.Web.Core; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public class UserProfileViewModel { - public class UserProfileViewModel - { - public const string AllowedImages = ".png,.jpg,.jpeg,.gif"; + public const string AllowedImages = ".png,.jpg,.jpeg,.gif"; - [Required(ErrorMessage = "(*)")] - [Display(Name = "نام کاربری")] - [Remote("ValidateUsername", "UserProfile", - AdditionalFields = nameof(Email) + "," + ViewModelConstants.AntiForgeryToken + "," + nameof(Pid), - HttpMethod = "POST")] - public string UserName { get; set; } + [Required(ErrorMessage = "(*)")] + [Display(Name = "نام کاربری")] + [Remote("ValidateUsername", "UserProfile", + AdditionalFields = nameof(Email) + "," + ViewModelConstants.AntiForgeryToken + "," + nameof(Pid), + HttpMethod = "POST")] + public string UserName { get; set; } - [Display(Name = "نام")] - [Required(ErrorMessage = "(*)")] - [StringLength(450)] - public string FirstName { get; set; } + [Display(Name = "نام")] + [Required(ErrorMessage = "(*)")] + [StringLength(450)] + public string FirstName { get; set; } - [Display(Name = "نام خانوادگی")] - [Required(ErrorMessage = "(*)")] - [StringLength(450)] - public string LastName { get; set; } + [Display(Name = "نام خانوادگی")] + [Required(ErrorMessage = "(*)")] + [StringLength(450)] + public string LastName { get; set; } - [Required(ErrorMessage = "(*)")] - [Remote("ValidateUsername", "UserProfile", - AdditionalFields = nameof(UserName) + "," + ViewModelConstants.AntiForgeryToken + "," + nameof(Pid), - HttpMethod = "POST")] - [EmailAddress(ErrorMessage = "لطفا آدرس ایمیل معتبری را وارد نمائید.")] - [Display(Name = "ایمیل")] - public string Email { get; set; } + [Required(ErrorMessage = "(*)")] + [Remote("ValidateUsername", "UserProfile", + AdditionalFields = nameof(UserName) + "," + ViewModelConstants.AntiForgeryToken + "," + nameof(Pid), + HttpMethod = "POST")] + [EmailAddress(ErrorMessage = "لطفا آدرس ایمیل معتبری را وارد نمائید.")] + [Display(Name = "ایمیل")] + public string Email { get; set; } - [Display(Name = "تصویر")] - [StringLength(maximumLength: 1000, ErrorMessage = "حداکثر طول آدرس تصویر 1000 حرف است.")] - public string PhotoFileName { set; get; } + [Display(Name = "تصویر")] + [StringLength(maximumLength: 1000, ErrorMessage = "حداکثر طول آدرس تصویر 1000 حرف است.")] + public string PhotoFileName { set; get; } - [UploadFileExtensions(AllowedImages, - ErrorMessage = "لطفا تنها یک تصویر " + AllowedImages + " را ارسال نمائید.")] - [DataType(DataType.Upload)] - public IFormFile Photo { get; set; } + [UploadFileExtensions(AllowedImages, + ErrorMessage = "لطفا تنها یک تصویر " + AllowedImages + " را ارسال نمائید.")] + [DataType(DataType.Upload)] + public IFormFile Photo { get; set; } - public int? DateOfBirthYear { set; get; } - public int? DateOfBirthMonth { set; get; } - public int? DateOfBirthDay { set; get; } + public int? DateOfBirthYear { set; get; } + public int? DateOfBirthMonth { set; get; } + public int? DateOfBirthDay { set; get; } - [Display(Name = "محل اقامت")] - public string Location { set; get; } + [Display(Name = "محل اقامت")] + public string Location { set; get; } - [Display(Name = "نمایش عمومی ایمیل")] - public bool IsEmailPublic { set; get; } + [Display(Name = "نمایش عمومی ایمیل")] + public bool IsEmailPublic { set; get; } - [Display(Name = "فعال‌سازی اعتبار سنجی دو مرحله‌ای")] - public bool TwoFactorEnabled { set; get; } + [Display(Name = "فعال‌سازی اعتبار سنجی دو مرحله‌ای")] + public bool TwoFactorEnabled { set; get; } - public bool IsPasswordTooOld { set; get; } + public bool IsPasswordTooOld { set; get; } - [HiddenInput] - public string Pid { set; get; } + [HiddenInput] + public string Pid { set; get; } - [HiddenInput] - public bool IsAdminEdit { set; get; } - } + [HiddenInput] + public bool IsAdminEdit { set; get; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/VerifyCodeViewModel.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/VerifyCodeViewModel.cs index 75e682d..86a8a49 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/VerifyCodeViewModel.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/VerifyCodeViewModel.cs @@ -1,22 +1,17 @@ -using System.ComponentModel.DataAnnotations; +namespace ASPNETCoreIdentitySample.ViewModels.Identity; -namespace ASPNETCoreIdentitySample.ViewModels.Identity +public class VerifyCodeViewModel { - public class VerifyCodeViewModel - { - [Required] - public string Provider { get; set; } + [Required] public string Provider { get; set; } - [Display(Name = "کد امنیتی")] - [Required(ErrorMessage = "(*)")] - public string Code { get; set; } + [Display(Name = "کد امنیتی"), Required(ErrorMessage = "(*)")] + public string Code { get; set; } - public string ReturnUrl { get; set; } + public string ReturnUrl { get; set; } - [Display(Name = "به‌خاطر سپاری مرورگر جاری؟")] - public bool RememberBrowser { get; set; } + [Display(Name = "به‌خاطر سپاری مرورگر جاری؟")] + public bool RememberBrowser { get; set; } - [Display(Name = "به‌خاطر سپاری اعتبارسنجی؟")] - public bool RememberMe { get; set; } - } + [Display(Name = "به‌خاطر سپاری اعتبارسنجی؟")] + public bool RememberMe { get; set; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ViewModelConstants.cs b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ViewModelConstants.cs index 61f608c..daf5f4e 100644 --- a/src/ASPNETCoreIdentitySample.ViewModels/Identity/ViewModelConstants.cs +++ b/src/ASPNETCoreIdentitySample.ViewModels/Identity/ViewModelConstants.cs @@ -1,7 +1,6 @@ -namespace ASPNETCoreIdentitySample.ViewModels.Identity +namespace ASPNETCoreIdentitySample.ViewModels.Identity; + +public static class ViewModelConstants { - public static class ViewModelConstants - { - public const string AntiForgeryToken = "__RequestVerificationToken"; - } + public const string AntiForgeryToken = "__RequestVerificationToken"; } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/ASPNETCoreIdentitySample.csproj b/src/ASPNETCoreIdentitySample/ASPNETCoreIdentitySample.csproj index d7c4183..f94a664 100644 --- a/src/ASPNETCoreIdentitySample/ASPNETCoreIdentitySample.csproj +++ b/src/ASPNETCoreIdentitySample/ASPNETCoreIdentitySample.csproj @@ -1,8 +1,7 @@  - net5.0 + net6.0 Exe - RCS1090 @@ -18,13 +17,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/AreaConstants.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/AreaConstants.cs index 94f27c5..c4fd480 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/AreaConstants.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/AreaConstants.cs @@ -1,10 +1,9 @@ -namespace ASPNETCoreIdentitySample.Areas.Identity +namespace ASPNETCoreIdentitySample.Areas.Identity; + +/// +/// More info: http://www.dntips.ir/post/2550 +/// +public static class AreaConstants { - /// - /// More info: http://www.dotnettips.info/post/2550 - /// - public static class AreaConstants - { - public const string IdentityArea = "Identity"; - } + public const string IdentityArea = "Identity"; } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangePasswordController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangePasswordController.cs index 3032bee..a822d38 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangePasswordController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangePasswordController.cs @@ -1,116 +1,115 @@ using ASPNETCoreIdentitySample.Common.IdentityToolkit; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Emails; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; +using DNTCommon.Web.Core; using DNTPersianUtils.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using System.Threading.Tasks; -using System; -using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; + +[Authorize, Area(AreaConstants.IdentityArea), + BreadCrumb(Title = "تغییر کلمه‌ی عبور", UseDefaultRouteUrl = true, Order = 0)] +public class ChangePasswordController : Controller { - [Authorize] - [Area(AreaConstants.IdentityArea)] - [BreadCrumb(Title = "تغییر کلمه‌ی عبور", UseDefaultRouteUrl = true, Order = 0)] - public class ChangePasswordController : 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) { - private readonly IEmailSender _emailSender; - private readonly IApplicationUserManager _userManager; - private readonly IApplicationSignInManager _signInManager; - private readonly IPasswordValidator _passwordValidator; - private readonly IUsedPasswordsService _usedPasswordsService; - private readonly 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)); + } - public ChangePasswordController( - IApplicationUserManager userManager, - IApplicationSignInManager signInManager, - IEmailSender emailSender, - IPasswordValidator passwordValidator, - IUsedPasswordsService usedPasswordsService, - IOptionsSnapshot siteOptions) + [BreadCrumb(Title = "ایندکس", Order = 1)] + public async Task Index() + { + var userId = User.Identity?.GetUserId() ?? 0; + var passwordChangeDate = await _usedPasswordsService.GetLastUserPasswordChangeDateAsync(userId); + return View(new ChangePasswordViewModel { - _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)); - } + LastUserPasswordChangeDate = passwordChangeDate + }); + } - [BreadCrumb(Title = "ایندکس", Order = 1)] - public async Task Index() + [HttpPost, ValidateAntiForgeryToken] + public async Task Index(ChangePasswordViewModel model) + { + if (model is null) { - var userId = this.User.Identity.GetUserId(); - var passwordChangeDate = await _usedPasswordsService.GetLastUserPasswordChangeDateAsync(userId); - return View(model: new ChangePasswordViewModel - { - LastUserPasswordChangeDate = passwordChangeDate - }); + return View("Error"); } - /// - /// For [Remote] validation - /// - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] - [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] - public async Task ValidatePassword(string newPassword) + if (!ModelState.IsValid) { - var user = await _userManager.GetCurrentUserAsync(); - var result = await _passwordValidator.ValidateAsync( - (UserManager)_userManager, user, newPassword); - return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); + return View(model); } - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Index(ChangePasswordViewModel model) + var user = await _userManager.GetCurrentUserAsync(); + if (user == null) { - if (!ModelState.IsValid) - { - return View(model); - } + return View("NotFound"); + } - var user = await _userManager.GetCurrentUserAsync(); - if (user == null) - { - return View("NotFound"); - } + var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); + if (result.Succeeded) + { + await _userManager.UpdateSecurityStampAsync(user); - var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); - if (result.Succeeded) - { - await _userManager.UpdateSecurityStampAsync(user); + // reflect the changes in the Identity cookie + await _signInManager.RefreshSignInAsync(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() + }); - await _emailSender.SendEmailAsync( - email: user.Email, - subject: "اطلاع رسانی تغییر کلمه‌ی عبور", - viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_ChangePasswordNotification.cshtml", - model: new ChangePasswordNotificationViewModel - { - User = user, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() - }); + return RedirectToAction(nameof(Index), "UserCard", new { id = user.Id }); + } - return RedirectToAction(nameof(Index), "UserCard", routeValues: new { id = user.Id }); - } + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } + return View(model); + } - return View(model); - } + /// + /// For [Remote] validation + /// + [AjaxOnly, HttpPost, ValidateAntiForgeryToken, 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)); } } \ 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 31bf8b5..3258400 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangeUserPasswordController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ChangeUserPasswordController.cs @@ -1,127 +1,126 @@ using ASPNETCoreIdentitySample.Common.IdentityToolkit; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.Services.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Emails; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; +using DNTCommon.Web.Core; using DNTPersianUtils.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using System.Threading.Tasks; -using System; -using DNTCommon.Web.Core; -using ASPNETCoreIdentitySample.Services.Identity; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; + +[Authorize(Roles = ConstantRoles.Admin), Area(AreaConstants.IdentityArea), + BreadCrumb(Title = "تغییر کلمه‌ی عبور كاربر توسط مدير سيستم", UseDefaultRouteUrl = true, Order = 0)] +public class ChangeUserPasswordController : Controller { - [Authorize(Roles = ConstantRoles.Admin)] - [Area(AreaConstants.IdentityArea)] - [BreadCrumb(Title = "تغییر کلمه‌ی عبور كاربر توسط مدير سيستم", UseDefaultRouteUrl = true, Order = 0)] - public class ChangeUserPasswordController : 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) { - private readonly IEmailSender _emailSender; - private readonly IApplicationUserManager _userManager; - private readonly IApplicationSignInManager _signInManager; - private readonly IPasswordValidator _passwordValidator; - private readonly IUsedPasswordsService _usedPasswordsService; - private readonly IOptionsSnapshot _siteOptions; - - 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) { - _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)); + return View("NotFound"); } - [BreadCrumb(Title = "ایندکس", Order = 1)] - public async Task Index(int? id) + var user = await _userManager.FindByIdAsync(id.Value.ToString(CultureInfo.InvariantCulture)); + if (user == null) { - if (!id.HasValue) - { - return View("NotFound"); - } - - var user = await _userManager.FindByIdAsync(id.Value.ToString()); - if (user == null) - { - return View("NotFound"); - } - - return View(model: new ChangeUserPasswordViewModel - { - UserId = user.Id, - Name = user.UserName - }); + return View("NotFound"); } - /// - /// For [Remote] validation - /// - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] - [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] - public async Task ValidatePassword(string newPassword, int userId) + return View(new ChangeUserPasswordViewModel { - var user = await _userManager.FindByIdAsync(userId.ToString()); - var result = await _passwordValidator.ValidateAsync( - (UserManager)_userManager, user, newPassword); - return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); - } + UserId = user.Id, + Name = user.UserName + }); + } - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Index(ChangeUserPasswordViewModel model) + [HttpPost, ValidateAntiForgeryToken] + public async Task Index(ChangeUserPasswordViewModel model) + { + if (model is null) { - if (!ModelState.IsValid) - { - return View(model); - } - - var user = await _userManager.FindByIdAsync(model.UserId.ToString()); - if (user == null) - { - return View("NotFound"); - } - - var result = await _userManager.UpdatePasswordHash(user, model.NewPassword, validatePassword: true); - if (result.Succeeded) - { - await _userManager.UpdateSecurityStampAsync(user); - - // reflect the changes in the Identity cookie - await _signInManager.RefreshSignInAsync(user); - - await _emailSender.SendEmailAsync( - email: user.Email, - subject: "اطلاع رسانی تغییر کلمه‌ی عبور", - viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_ChangePasswordNotification.cshtml", - model: new ChangePasswordNotificationViewModel - { - User = user, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() - }); - - return RedirectToAction(nameof(Index), "UserCard", routeValues: new { id = user.Id }); - } - - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } + return View("Error"); + } + if (!ModelState.IsValid) + { return View(model); } + + var user = await _userManager.FindByIdAsync(model.UserId.ToString(CultureInfo.InvariantCulture)); + if (user == null) + { + return View("NotFound"); + } + + var result = await _userManager.UpdatePasswordHash(user, model.NewPassword, true); + if (result.Succeeded) + { + 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 }); + } + + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + + return View(model); + } + + /// + /// For [Remote] validation + /// + [AjaxOnly, HttpPost, ValidateAntiForgeryToken, 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)); } } \ 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 fc2468f..b2a378b 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicPermissionsAreaSampleController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicPermissionsAreaSampleController.cs @@ -1,26 +1,21 @@ using System.ComponentModel; using ASPNETCoreIdentitySample.Services.Identity; -using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +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("کنترلر نمونه با سطح دسترسی پویا در يك ناحيه خاص")] +public class DynamicPermissionsAreaSampleController : Controller { - /// - /// More info: http://www.dotnettips.info/post/2581 - /// - [Authorize(Policy = ConstantPolicies.DynamicPermission)] - [Area(AreaConstants.IdentityArea)] - [BreadCrumb(UseDefaultRouteUrl = true, Order = 0)] - [DisplayName("کنترلر نمونه با سطح دسترسی پویا در يك ناحيه خاص")] - public class DynamicPermissionsAreaSampleController : Controller + [DisplayName("ایندکس"), BreadCrumb(Order = 1)] + public IActionResult Index() { - [DisplayName("ایندکس")] - [BreadCrumb(Order = 1)] - public IActionResult Index() - { - return View(); - } + return 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 1e7cc0e..ab5f220 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicRoleClaimsManagerController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/DynamicRoleClaimsManagerController.cs @@ -6,72 +6,75 @@ using DNTCommon.Web.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using System; -using System.Threading.Tasks; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +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 { - /// - /// More info: http://www.dotnettips.info/post/2581 - /// - [Authorize(Roles = ConstantRoles.Admin)] - [Area(AreaConstants.IdentityArea)] - [BreadCrumb(Title = "مدیریت نقش‌های پویا", UseDefaultRouteUrl = true, Order = 0)] - public class DynamicRoleClaimsManagerController : Controller + private readonly IMvcActionsDiscoveryService _mvcActionsDiscoveryService; + private readonly IApplicationRoleManager _roleManager; + + public DynamicRoleClaimsManagerController( + IMvcActionsDiscoveryService mvcActionsDiscoveryService, + IApplicationRoleManager roleManager) { - private readonly IMvcActionsDiscoveryService _mvcActionsDiscoveryService; - private readonly IApplicationRoleManager _roleManager; + _mvcActionsDiscoveryService = mvcActionsDiscoveryService ?? + throw new ArgumentNullException(nameof(mvcActionsDiscoveryService)); + _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); + } - public DynamicRoleClaimsManagerController( - IMvcActionsDiscoveryService mvcActionsDiscoveryService, - IApplicationRoleManager roleManager) + [BreadCrumb(Title = "ایندکس", Order = 1)] + public async Task Index(int? id) + { + this.AddBreadCrumb(new BreadCrumb { - _mvcActionsDiscoveryService = mvcActionsDiscoveryService ?? throw new ArgumentNullException(nameof(mvcActionsDiscoveryService)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); - } + Title = "مدیریت نقش‌ها", + Url = Url.Action("Index", "RolesManager"), + Order = -1 + }); - [BreadCrumb(Title = "ایندکس", Order = 1)] - public async Task Index(int? id) + if (!id.HasValue) { - this.AddBreadCrumb(new BreadCrumb - { - Title = "مدیریت نقش‌ها", - Url = Url.Action("Index", "RolesManager"), - Order = -1 - }); + return View("Error"); + } - if (!id.HasValue) - { - return View("Error"); - } + var role = await _roleManager.FindRoleIncludeRoleClaimsAsync(id.Value); + if (role == null) + { + return View("NotFound"); + } - var role = await _roleManager.FindRoleIncludeRoleClaimsAsync(id.Value); - if (role == null) - { - return View("NotFound"); - } + var securedControllerActions = + _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission); + return View(new DynamicRoleClaimsManagerViewModel + { + SecuredControllerActions = securedControllerActions, + RoleIncludeRoleClaims = role + }); + } - var securedControllerActions = _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission); - return View(model: new DynamicRoleClaimsManagerViewModel - { - SecuredControllerActions = securedControllerActions, - RoleIncludeRoleClaims = role - }); + [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + public async Task Index(DynamicRoleClaimsManagerViewModel model) + { + if (model is null) + { + return View("Error"); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] - [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] - public async Task Index(DynamicRoleClaimsManagerViewModel model) + var result = await _roleManager.AddOrUpdateRoleClaimsAsync( + model.RoleId, + ConstantPolicies.DynamicPermissionClaimType, + model.ActionIds); + if (!result.Succeeded) { - var result = await _roleManager.AddOrUpdateRoleClaimsAsync( - roleId: model.RoleId, - roleClaimType: ConstantPolicies.DynamicPermissionClaimType, - selectedRoleClaimValues: model.ActionIds); - if (!result.Succeeded) - { - return BadRequest(error: result.DumpErrors(useHtmlNewLine: true)); - } - return Json(new { success = true }); + return BadRequest(result.DumpErrors(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 ab86dcb..e353211 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ForgotPasswordController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/ForgotPasswordController.cs @@ -2,146 +2,150 @@ using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity.Emails; +using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using DNTBreadCrumb.Core; using DNTCaptcha.Core; +using DNTCommon.Web.Core; +using DNTPersianUtils.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using System.Threading.Tasks; -using System; -using ASPNETCoreIdentitySample.ViewModels.Identity.Emails; -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using DNTPersianUtils.Core; -using DNTCommon.Web.Core; +using Language = DNTCaptcha.Core.Language; + +namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +[Area(AreaConstants.IdentityArea), AllowAnonymous, + BreadCrumb(Title = "بازیابی کلمه‌ی عبور", UseDefaultRouteUrl = true, Order = 0)] +public class ForgotPasswordController : Controller { - [Area(AreaConstants.IdentityArea)] - [AllowAnonymous] - [BreadCrumb(Title = "بازیابی کلمه‌ی عبور", UseDefaultRouteUrl = true, Order = 0)] - public class ForgotPasswordController : 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) { - private readonly IEmailSender _emailSender; - private readonly IApplicationUserManager _userManager; - private readonly IPasswordValidator _passwordValidator; - private readonly IOptionsSnapshot _siteOptions; - - 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)); - } + _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() - { - return View(); - } + [BreadCrumb(Title = "تائید کلمه‌ی عبور فراموش شده", Order = 1)] + public IActionResult ForgotPasswordConfirmation() + { + return View(); + } - [BreadCrumb(Title = "ایندکس", Order = 1)] - public IActionResult Index() + [BreadCrumb(Title = "ایندکس", Order = 1)] + public IActionResult Index() + { + return View(); + } + + [HttpPost, ValidateAntiForgeryToken, ValidateDNTCaptcha(CaptchaGeneratorLanguage = Language.Persian, + CaptchaGeneratorDisplayMode = DisplayMode.SumOfTwoNumbers)] + public async Task Index(ForgotPasswordViewModel model) + { + if (model is null) { - return View(); + return View("Error"); } - /// - /// For [Remote] validation - /// - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] - [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] - public async Task ValidatePassword(string password, string email) + if (ModelState.IsValid) { - var user = await _userManager.FindByEmailAsync(email); - if (user == null) + var user = await _userManager.FindByEmailAsync(model.Email); + if (user == null || !await _userManager.IsEmailConfirmedAsync(user)) { - return Json("ایمیل وارد شده معتبر نیست."); + return View("Error"); } - var result = await _passwordValidator.ValidateAsync( - (UserManager)_userManager, user, password); - return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); + 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"); } - [HttpPost] - [ValidateAntiForgeryToken] - [ValidateDNTCaptcha(CaptchaGeneratorLanguage = DNTCaptcha.Core.Language.Persian, - CaptchaGeneratorDisplayMode = DisplayMode.SumOfTwoNumbers)] - public async Task Index(ForgotPasswordViewModel model) + return View(model); + } + + /// + /// For [Remote] validation + /// + [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + public async Task ValidatePassword(string password, string email) + { + var user = await _userManager.FindByEmailAsync(email); + if (user == null) { - if (ModelState.IsValid) - { - var user = await _userManager.FindByEmailAsync(model.Email); - if (user == null || !await _userManager.IsEmailConfirmedAsync(user)) - { - return View("Error"); - } - - var code = await _userManager.GeneratePasswordResetTokenAsync(user); - await _emailSender.SendEmailAsync( - email: model.Email, - subject: "بازیابی کلمه‌ی عبور", - viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_PasswordReset.cshtml", - model: new PasswordResetViewModel - { - UserId = user.Id, - Token = code, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() - }) - ; - - return View("ForgotPasswordConfirmation"); - } - return View(model); + return Json("ایمیل وارد شده معتبر نیست."); } - [BreadCrumb(Title = "تغییر کلمه‌ی عبور", Order = 1)] - public IActionResult ResetPassword(string code = null) + var result = await _passwordValidator.ValidateAsync( + (UserManager)_userManager, user, password); + return Json(result.Succeeded ? "true" : result.DumpErrors(true)); + } + + [BreadCrumb(Title = "تغییر کلمه‌ی عبور", Order = 1)] + public IActionResult ResetPassword(string code = null) + { + return code == null ? View("Error") : View(); + } + + [HttpPost, ValidateAntiForgeryToken] + public async Task ResetPassword(ResetPasswordViewModel model) + { + if (model is null) { - return code == null ? View("Error") : View(); + return View("Error"); } - [HttpPost] - [ValidateAntiForgeryToken] - public async Task ResetPassword(ResetPasswordViewModel model) + if (!ModelState.IsValid) { - if (!ModelState.IsValid) - { - return View(model); - } - - 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); - if (result.Succeeded) - { - return RedirectToAction(nameof(ResetPasswordConfirmation)); - } + return View(model); + } - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } + var user = await _userManager.FindByEmailAsync(model.Email); + if (user == null) + { + // Don't reveal that the user does not exist + return RedirectToAction(nameof(ResetPasswordConfirmation)); + } - return View(); + var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password); + if (result.Succeeded) + { + return RedirectToAction(nameof(ResetPasswordConfirmation)); } - [BreadCrumb(Title = "تائیدیه تغییر کلمه‌ی عبور", Order = 1)] - public IActionResult ResetPasswordConfirmation() + foreach (var error in result.Errors) { - return View(); + ModelState.AddModelError(string.Empty, error.Description); } + + return View(); + } + + [BreadCrumb(Title = "تائیدیه تغییر کلمه‌ی عبور", Order = 1)] + public IActionResult ResetPasswordConfirmation() + { + return View(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/HomeController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/HomeController.cs index 17b5c37..ff672b2 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/HomeController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/HomeController.cs @@ -2,14 +2,14 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; + +[Area(AreaConstants.IdentityArea), Authorize, BreadCrumb(Title = "خانه", UseDefaultRouteUrl = true, Order = 0)] +public class HomeController : Controller { - [Area(AreaConstants.IdentityArea)] - [Authorize] - [BreadCrumb(Title = "خانه", UseDefaultRouteUrl = true, Order = 0)] - public class HomeController : Controller + [BreadCrumb(Title = "ایندکس", Order = 1)] + public IActionResult Index() { - [BreadCrumb(Title = "ایندکس", Order = 1)] - public IActionResult Index() => View(); + return 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 bd59afa..00cd788 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/LoginController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/LoginController.cs @@ -1,131 +1,125 @@ using ASPNETCoreIdentitySample.Services.Contracts.Identity; -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using ASPNETCoreIdentitySample.ViewModels.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using DNTBreadCrumb.Core; using DNTCaptcha.Core; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Threading.Tasks; -using DNTCommon.Web.Core; -using System; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; + +[Area(AreaConstants.IdentityArea), AllowAnonymous, + BreadCrumb(Title = "ورود به سیستم", UseDefaultRouteUrl = true, Order = 0)] +public class LoginController : Controller { - [Area(AreaConstants.IdentityArea)] - [AllowAnonymous] - [BreadCrumb(Title = "ورود به سیستم", UseDefaultRouteUrl = true, Order = 0)] - public class LoginController : Controller + private readonly IApplicationSignInManager _signInManager; + private readonly IOptionsSnapshot _siteOptions; + private readonly IApplicationUserManager _userManager; + + public LoginController( + IApplicationSignInManager signInManager, + IApplicationUserManager userManager, + IOptionsSnapshot siteOptions) { - private readonly ILogger _logger; - private readonly IApplicationSignInManager _signInManager; - private readonly IApplicationUserManager _userManager; - private readonly IOptionsSnapshot _siteOptions; - - public LoginController( - IApplicationSignInManager signInManager, - IApplicationUserManager userManager, - IOptionsSnapshot siteOptions, - ILogger logger) - { - _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _siteOptions = siteOptions ?? throw new ArgumentNullException(nameof(siteOptions)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } + _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; + return View(); + } - [BreadCrumb(Title = "ایندکس", Order = 1)] - [NoBrowserCache] - public IActionResult Index(string returnUrl = null) + [HttpPost, ValidateAntiForgeryToken, ValidateDNTCaptcha(CaptchaGeneratorLanguage = Language.Persian, + CaptchaGeneratorDisplayMode = DisplayMode.SumOfTwoNumbers)] + public async Task Index(LoginViewModel model, string returnUrl = null) + { + if (model is null) { - ViewData["ReturnUrl"] = returnUrl; - return View(); + return View("Error"); } - [HttpPost] - [ValidateAntiForgeryToken] - [ValidateDNTCaptcha(CaptchaGeneratorLanguage = Language.Persian, - CaptchaGeneratorDisplayMode = DisplayMode.SumOfTwoNumbers)] - public async Task Index(LoginViewModel model, string returnUrl = null) + ViewData["ReturnUrl"] = returnUrl; + if (ModelState.IsValid) { - ViewData["ReturnUrl"] = returnUrl; - if (ModelState.IsValid) + var user = await _userManager.FindByNameAsync(model.Username); + if (user == null) { - var user = await _userManager.FindByNameAsync(model.Username); - if (user == null) - { - ModelState.AddModelError(string.Empty, "نام کاربری و یا کلمه‌ی عبور وارد شده معتبر نیستند."); - return View(model); - } + ModelState.AddModelError(string.Empty, "نام کاربری و یا کلمه‌ی عبور وارد شده معتبر نیستند."); + return View(model); + } - if (!user.IsActive) - { - ModelState.AddModelError(string.Empty, "اکانت شما غیرفعال شده‌است."); - return View(model); - } + if (!user.IsActive) + { + ModelState.AddModelError(string.Empty, "اکانت شما غیرفعال شده‌است."); + return View(model); + } - if (_siteOptions.Value.EnableEmailConfirmation && - !await _userManager.IsEmailConfirmedAsync(user)) - { - ModelState.AddModelError("", "لطفا به پست الکترونیک خود مراجعه کرده و ایمیل خود را تائید کنید!"); - return View(model); - } + if (_siteOptions.Value.EnableEmailConfirmation && + !await _userManager.IsEmailConfirmedAsync(user)) + { + ModelState.AddModelError("", "لطفا به پست الکترونیک خود مراجعه کرده و ایمیل خود را تائید کنید!"); + return View(model); + } - var result = await _signInManager.PasswordSignInAsync( - model.Username, - model.Password, - model.RememberMe, - lockoutOnFailure: true); - if (result.Succeeded) + var result = await _signInManager.PasswordSignInAsync( + model.Username, + model.Password, + model.RememberMe, + true); + if (result.Succeeded) + { + if (Url.IsLocalUrl(returnUrl)) { - _logger.LogInformation(1, $"{model.Username} logged in."); - if (Url.IsLocalUrl(returnUrl)) - { - return Redirect(returnUrl); - } - return RedirectToAction(nameof(HomeController.Index), "Home"); + return Redirect(returnUrl); } - if (result.RequiresTwoFactor) - { - return RedirectToAction( - nameof(TwoFactorController.SendCode), - "TwoFactor", - new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); - } + return RedirectToAction(nameof(HomeController.Index), "Home"); + } - if (result.IsLockedOut) - { - _logger.LogWarning(2, $"{model.Username} قفل شده‌است."); - return View("~/Areas/Identity/Views/TwoFactor/Lockout.cshtml"); - } + if (result.RequiresTwoFactor) + { + return RedirectToAction( + nameof(TwoFactorController.SendCode), + "TwoFactor", + new { ReturnUrl = returnUrl, model.RememberMe }); + } - if (result.IsNotAllowed) - { - ModelState.AddModelError(string.Empty, "عدم دسترسی ورود."); - return View(model); - } + if (result.IsLockedOut) + { + return View("~/Areas/Identity/Views/TwoFactor/Lockout.cshtml"); + } - ModelState.AddModelError(string.Empty, "نام کاربری و یا کلمه‌ی عبور وارد شده معتبر نیستند."); + if (result.IsNotAllowed) + { + ModelState.AddModelError(string.Empty, "عدم دسترسی ورود."); return View(model); } - // If we got this far, something failed, redisplay form + ModelState.AddModelError(string.Empty, "نام کاربری و یا کلمه‌ی عبور وارد شده معتبر نیستند."); return View(model); } - public async Task LogOff() - { - var user = User.Identity.IsAuthenticated ? await _userManager.FindByNameAsync(User.Identity.Name) : null; - await _signInManager.SignOutAsync(); - if (user != null) - { - await _userManager.UpdateSecurityStampAsync(user); - _logger.LogInformation(4, $"{user.UserName} logged out."); - } + // If we got this far, something failed, redisplay form + return View(model); + } - return RedirectToAction(nameof(HomeController.Index), "Home"); + public async Task LogOff() + { + var user = User.Identity is { IsAuthenticated: true } + ? await _userManager.FindByNameAsync(User.Identity.Name) + : null; + await _signInManager.SignOutAsync(); + if (user != null) + { + await _userManager.UpdateSecurityStampAsync(user); } + + return RedirectToAction(nameof(HomeController.Index), "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 3d9b91d..e6917a9 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RegisterController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RegisterController.cs @@ -1,160 +1,152 @@ -using ASPNETCoreIdentitySample.Common.GuardToolkit; -using ASPNETCoreIdentitySample.Common.IdentityToolkit; +using ASPNETCoreIdentitySample.Common.IdentityToolkit; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity.Emails; +using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; using DNTBreadCrumb.Core; using DNTCaptcha.Core; +using DNTCommon.Web.Core; +using DNTPersianUtils.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Threading.Tasks; -using System; -using ASPNETCoreIdentitySample.ViewModels.Identity.Emails; -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using DNTPersianUtils.Core; -using DNTCommon.Web.Core; +using Language = DNTCaptcha.Core.Language; + +namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +[Area(AreaConstants.IdentityArea), AllowAnonymous, BreadCrumb(Title = "ثبت نام", UseDefaultRouteUrl = true, Order = 0)] +public class RegisterController : Controller { - [Area(AreaConstants.IdentityArea)] - [AllowAnonymous] - [BreadCrumb(Title = "ثبت نام", UseDefaultRouteUrl = true, Order = 0)] - public class RegisterController : 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) { - private readonly IEmailSender _emailSender; - private readonly ILogger _logger; - private readonly IApplicationUserManager _userManager; - private readonly IPasswordValidator _passwordValidator; - private readonly IUserValidator _userValidator; - private readonly IOptionsSnapshot _siteOptions; - - public RegisterController( - IApplicationUserManager userManager, - IPasswordValidator passwordValidator, - IUserValidator userValidator, - IEmailSender emailSender, - IOptionsSnapshot siteOptions, - ILogger logger) - { - _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)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _siteOptions = siteOptions ?? throw new ArgumentNullException(nameof(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 - /// - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] - [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(useHtmlNewLine: true)); - } + /// + /// For [Remote] validation + /// + [AjaxOnly, HttpPost, ValidateAntiForgeryToken, 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)); + } + + /// + /// For [Remote] validation + /// + [AjaxOnly, HttpPost, ValidateAntiForgeryToken, 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)); + } - /// - /// For [Remote] validation - /// - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] - [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] - public async Task ValidatePassword(string password, string username) + [BreadCrumb(Title = "تائید ایمیل", Order = 1)] + public async Task ConfirmEmail(string userId, string code) + { + if (userId == null || code == null) { - var result = await _passwordValidator.ValidateAsync( - (UserManager)_userManager, new User { UserName = username }, password); - return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); + return View("Error"); } - [BreadCrumb(Title = "تائید ایمیل", Order = 1)] - public async Task ConfirmEmail(string userId, string code) + var user = await _userManager.FindByIdAsync(userId); + if (user == null) { - if (userId == null || code == null) - { - return View("Error"); - } + return View("NotFound"); + } - var user = await _userManager.FindByIdAsync(userId); - if (user == null) - { - return View("NotFound"); - } + var result = await _userManager.ConfirmEmailAsync(user, code); + return View(result.Succeeded ? nameof(ConfirmEmail) : "Error"); + } - var result = await _userManager.ConfirmEmailAsync(user, code); - return View(result.Succeeded ? nameof(ConfirmEmail) : "Error"); - } + [BreadCrumb(Title = "تائیدیه ایمیل", Order = 1)] + public IActionResult ConfirmedRegisteration() + { + return View(); + } - [BreadCrumb(Title = "ایندکس", Order = 1)] - public IActionResult Index() - { - return View(); - } + [BreadCrumb(Title = "ایندکس", Order = 1)] + public IActionResult Index() + { + return View(); + } - [BreadCrumb(Title = "تائیدیه ایمیل", Order = 1)] - public IActionResult ConfirmedRegisteration() + [HttpPost, ValidateAntiForgeryToken, ValidateDNTCaptcha(CaptchaGeneratorLanguage = Language.Persian, + CaptchaGeneratorDisplayMode = DisplayMode.SumOfTwoNumbers)] + public async Task Index(RegisterViewModel model) + { + if (model is null) { - return View(); + return View("Error"); } - [HttpPost] - [ValidateAntiForgeryToken] - [ValidateDNTCaptcha(CaptchaGeneratorLanguage = DNTCaptcha.Core.Language.Persian, - CaptchaGeneratorDisplayMode = DisplayMode.SumOfTwoNumbers)] - public async Task Index(RegisterViewModel model) + if (ModelState.IsValid) { - if (ModelState.IsValid) + var user = new User { - var user = new User - { - UserName = model.Username, - Email = model.Email, - FirstName = model.FirstName, - LastName = model.LastName - }; - var result = await _userManager.CreateAsync(user, model.Password); - if (result.Succeeded) + 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) { - _logger.LogInformation(3, $"{user.UserName} created a new account with password."); - - if (_siteOptions.Value.EnableEmailConfirmation) - { - var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); - //ControllerExtensions.ShortControllerName(), //todo: use everywhere ................. - - await _emailSender.SendEmailAsync( - email: user.Email, - subject: "لطفا اکانت خود را تائید کنید", - viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_RegisterEmailConfirmation.cshtml", - model: new RegisterEmailConfirmationViewModel - { - User = user, - EmailConfirmationToken = code, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() - }); - - return RedirectToAction(nameof(ConfirmYourEmail)); - } - return RedirectToAction(nameof(ConfirmedRegisteration)); - } + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + //ControllerExtensions.ShortControllerName(), //TODO: use everywhere ................. - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); + 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() + }); + + return RedirectToAction(nameof(ConfirmYourEmail)); } + + return RedirectToAction(nameof(ConfirmedRegisteration)); } - return View(model); - } - [BreadCrumb(Title = "ایمیل خود را تائید کنید", Order = 1)] - public IActionResult ConfirmYourEmail() - { - return View(); + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } } + + return View(model); + } + + [BreadCrumb(Title = "ایمیل خود را تائید کنید", Order = 1)] + public IActionResult ConfirmYourEmail() + { + return View(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RolesManagerController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RolesManagerController.cs index 49563b3..3d0143a 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RolesManagerController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/RolesManagerController.cs @@ -1,183 +1,200 @@ using ASPNETCoreIdentitySample.Common.IdentityToolkit; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; -using ASPNETCoreIdentitySample.ViewModels.Identity; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; using ASPNETCoreIdentitySample.Services.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; using DNTCommon.Web.Core; -using System; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; -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 : Controller + private const string RoleNotFound = "نقش درخواستی یافت نشد."; + private const int DefaultPageSize = 7; + + private readonly IApplicationRoleManager _roleManager; + + public RolesManagerController(IApplicationRoleManager roleManager) { - private const string RoleNotFound = "نقش درخواستی یافت نشد."; - private const int DefaultPageSize = 7; + _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); + } - private readonly IApplicationRoleManager _roleManager; + [BreadCrumb(Title = "ایندکس", Order = 1)] + public IActionResult Index() + { + var roles = _roleManager.GetAllCustomRolesAndUsersCountList(); + return View(roles); + } - public RolesManagerController(IApplicationRoleManager roleManager) + [AjaxOnly] + public async Task RenderRole([FromBody] ModelIdViewModel model) + { + if (model is null) { - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); + return BadRequest(); } - [BreadCrumb(Title = "ایندکس", Order = 1)] - public IActionResult Index() + if (!ModelState.IsValid) { - var roles = _roleManager.GetAllCustomRolesAndUsersCountList(); - return View(roles); + return BadRequest(ModelState); } - [AjaxOnly] - public async Task RenderRole([FromBody] ModelIdViewModel model) + if (model.Id == 0) { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - if (model == null || model.Id == 0) - { - return PartialView("_Create", model: new RoleViewModel()); - } + return PartialView("_Create", new RoleViewModel()); + } - var role = await _roleManager.FindByIdAsync(model.Id.ToString()); - if (role == null) - { - ModelState.AddModelError("", RoleNotFound); - return PartialView("_Create", model: new RoleViewModel()); - } - return PartialView("_Create", model: new RoleViewModel { Id = role.Id.ToString(), Name = role.Name }); + var role = await _roleManager.FindByIdAsync(model.Id.ToString(CultureInfo.InvariantCulture)); + if (role == null) + { + ModelState.AddModelError("", RoleNotFound); + return PartialView("_Create", new RoleViewModel()); } - [AjaxOnly] - [HttpPost] - [ValidateAntiForgeryToken] - public async Task EditRole(RoleViewModel model) + return PartialView("_Create", + new RoleViewModel { Id = role.Id.ToString(CultureInfo.InvariantCulture), Name = role.Name }); + } + + [AjaxOnly, HttpPost, ValidateAntiForgeryToken] + public async Task EditRole(RoleViewModel model) + { + if (model is null) { - if (ModelState.IsValid) - { - var role = await _roleManager.FindByIdAsync(model.Id); - if (role == null) - { - ModelState.AddModelError("", RoleNotFound); - } - else - { - role.Name = model.Name; - var result = await _roleManager.UpdateAsync(role); - if (result.Succeeded) - { - return Json(new { success = true }); - } - ModelState.AddErrorsFromResult(result); - } - } - return PartialView("_Create", model: model); + return BadRequest(); } - [AjaxOnly] - [HttpPost] - [ValidateAntiForgeryToken] - public async Task AddRole(RoleViewModel model) + if (ModelState.IsValid) { - if (ModelState.IsValid) + var role = await _roleManager.FindByIdAsync(model.Id); + if (role == null) + { + ModelState.AddModelError("", RoleNotFound); + } + else { - var result = await _roleManager.CreateAsync(new Role(model.Name)); + role.Name = model.Name; + var result = await _roleManager.UpdateAsync(role); if (result.Succeeded) { return Json(new { success = true }); } + ModelState.AddErrorsFromResult(result); } - return PartialView("_Create", model: model); } - [AjaxOnly] - public async Task RenderDeleteRole([FromBody] ModelIdViewModel model) + return PartialView("_Create", model); + } + + [AjaxOnly, HttpPost, ValidateAntiForgeryToken] + public async Task AddRole(RoleViewModel model) + { + if (model is null) { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } + return BadRequest(); + } - if (model == null) + if (ModelState.IsValid) + { + var result = await _roleManager.CreateAsync(new Role(model.Name)); + if (result.Succeeded) { - return BadRequest("model is null."); + return Json(new { success = true }); } - var role = await _roleManager.FindByIdAsync(model.Id.ToString()); - if (role == null) - { - ModelState.AddModelError("", RoleNotFound); - return PartialView("_Delete", model: new RoleViewModel()); - } - return PartialView("_Delete", model: new RoleViewModel { Id = role.Id.ToString(), Name = role.Name }); + ModelState.AddErrorsFromResult(result); } - [AjaxOnly] - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Delete(RoleViewModel model) + return PartialView("_Create", model); + } + + [AjaxOnly] + public async Task RenderDeleteRole([FromBody] ModelIdViewModel model) + { + if (!ModelState.IsValid) { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } + return BadRequest(ModelState); + } - if (string.IsNullOrWhiteSpace(model?.Id)) - { - return BadRequest("model is null."); - } + if (model == null) + { + return BadRequest("model is null."); + } - var role = await _roleManager.FindByIdAsync(model.Id); - if (role == null) - { - ModelState.AddModelError("", RoleNotFound); - } - else - { - var result = await _roleManager.DeleteAsync(role); - if (result.Succeeded) - { - return Json(new { success = true }); - } - ModelState.AddErrorsFromResult(result); - } - return PartialView("_Delete", model: model); + var role = await _roleManager.FindByIdAsync(model.Id.ToString(CultureInfo.InvariantCulture)); + if (role == null) + { + ModelState.AddModelError("", RoleNotFound); + return PartialView("_Delete", new RoleViewModel()); + } + + return PartialView("_Delete", + new RoleViewModel { Id = role.Id.ToString(CultureInfo.InvariantCulture), Name = role.Name }); + } + + [AjaxOnly, HttpPost, ValidateAntiForgeryToken] + public async Task Delete(RoleViewModel model) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + if (string.IsNullOrWhiteSpace(model?.Id)) + { + return BadRequest("model is null."); } - [BreadCrumb(Title = "لیست کاربران دارای نقش", Order = 1)] - public async Task UsersInRole(int? id, int? page = 1, string field = "Id", SortOrder order = SortOrder.Ascending) + var role = await _roleManager.FindByIdAsync(model.Id); + if (role == null) + { + ModelState.AddModelError("", RoleNotFound); + } + else { - if (id == null) + var result = await _roleManager.DeleteAsync(role); + if (result.Succeeded) { - return View("Error"); + return Json(new { success = true }); } - var model = await _roleManager.GetPagedApplicationUsersInRoleListAsync( - roleId: id.Value, - pageNumber: page.Value - 1, - recordsPerPage: DefaultPageSize, - sortByField: field, - sortOrder: order, - showAllUsers: true); + ModelState.AddErrorsFromResult(result); + } - model.Paging.CurrentPage = page.Value; - model.Paging.ItemsPerPage = DefaultPageSize; - model.Paging.ShowFirstLast = true; + return PartialView("_Delete", model); + } - if (HttpContext.Request.IsAjaxRequest()) - { - return PartialView("~/Areas/Identity/Views/UsersManager/_UsersList.cshtml", model); - } - return View(model); + [BreadCrumb(Title = "لیست کاربران دارای نقش", Order = 1)] + public async Task UsersInRole(int? id, int? page = 1, string field = "Id", + SortOrder order = SortOrder.Ascending) + { + if (id == null) + { + return View("Error"); } + + var model = await _roleManager.GetPagedApplicationUsersInRoleListAsync( + id.Value, + page.Value - 1, + DefaultPageSize, + field, + order, + true); + + model.Paging.CurrentPage = page.Value; + model.Paging.ItemsPerPage = DefaultPageSize; + model.Paging.ShowFirstLast = true; + + if (HttpContext.Request.IsAjaxRequest()) + { + return PartialView("~/Areas/Identity/Views/UsersManager/_UsersList.cshtml", model); + } + + return View(model); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/SystemLogController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/SystemLogController.cs index b8653c7..faea996 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/SystemLogController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/SystemLogController.cs @@ -1,71 +1,67 @@ using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.Services.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using System; -using ASPNETCoreIdentitySample.ViewModels.Identity; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +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 : Controller + private readonly IAppLogItemsService _appLogItemsService; + + public SystemLogController( + IAppLogItemsService appLogItemsService) { - private readonly IAppLogItemsService _appLogItemsService; + _appLogItemsService = appLogItemsService ?? throw new ArgumentNullException(nameof(appLogItemsService)); + } - public SystemLogController( - IAppLogItemsService appLogItemsService) + [BreadCrumb(Title = "ایندکس", Order = 1)] + public async Task Index( + string logLevel = "", + int pageNumber = 1, + int pageSize = -1, + string sort = "desc") + { + var itemsPerPage = 10; + if (pageSize > 0) { - _appLogItemsService = appLogItemsService ?? throw new ArgumentNullException(nameof(appLogItemsService)); + itemsPerPage = pageSize; } - [BreadCrumb(Title = "ایندکس", Order = 1)] - 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, sort == "desc" ? SortOrder.Descending : SortOrder.Ascending, logLevel); - model.LogLevel = logLevel; - model.Paging.CurrentPage = pageNumber; - model.Paging.ItemsPerPage = itemsPerPage; - return View(model); - } + var model = await _appLogItemsService.GetPagedAppLogItemsAsync( + pageNumber, itemsPerPage, + string.Equals(sort, "desc", StringComparison.OrdinalIgnoreCase) + ? SortOrder.Descending + : SortOrder.Ascending, logLevel); + model.LogLevel = logLevel; + model.Paging.CurrentPage = pageNumber; + model.Paging.ItemsPerPage = itemsPerPage; + return View(model); + } - [HttpPost] - [ValidateAntiForgeryToken] - public async Task LogItemDelete(int id) - { - await _appLogItemsService.DeleteAsync(id); - return RedirectToAction(nameof(Index)); - } + [HttpPost, ValidateAntiForgeryToken] + public async Task LogItemDelete(int id) + { + await _appLogItemsService.DeleteAsync(id); + return RedirectToAction(nameof(Index)); + } - [HttpPost] - [ValidateAntiForgeryToken] - public async Task LogDeleteAll(string logLevel = "") - { - await _appLogItemsService.DeleteAllAsync(logLevel); - return RedirectToAction(nameof(Index)); - } + [HttpPost, ValidateAntiForgeryToken] + public async Task LogDeleteAll(string logLevel = "") + { + await _appLogItemsService.DeleteAllAsync(logLevel); + return RedirectToAction(nameof(Index)); + } - [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)); - } + [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 6b1fa4c..d61db0c 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/TwoFactorController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/TwoFactorController.cs @@ -1,131 +1,125 @@ -using ASPNETCoreIdentitySample.Common.GuardToolkit; -using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Emails; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; using DNTPersianUtils.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Threading.Tasks; -using System; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +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 : 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) { - private readonly IEmailSender _emailSender; - private readonly ILogger _logger; - private readonly IApplicationSignInManager _signInManager; - private readonly IApplicationUserManager _userManager; - private readonly IOptionsSnapshot _siteOptions; - - public TwoFactorController( - IApplicationUserManager userManager, - IApplicationSignInManager signInManager, - IEmailSender emailSender, - IOptionsSnapshot siteOptions, - ILogger logger) + _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)] + public async Task SendCode(string returnUrl = null, bool rememberMe = false) + { + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) { - _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)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + return View("NotFound"); } - [AllowAnonymous] - [BreadCrumb(Title = "ارسال کد", Order = 1)] - public async Task SendCode(string returnUrl = null, bool rememberMe = false) + var tokenProvider = "Email"; + var code = await _userManager.GenerateTwoFactorTokenAsync(user, tokenProvider); + if (string.IsNullOrWhiteSpace(code)) { - var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); - if (user == null) + return View("Error"); + } + + await _emailSender.SendEmailAsync( + user.Email, + "کد جدید اعتبارسنجی دو مرحله‌ای", + "~/Areas/Identity/Views/EmailTemplates/_TwoFactorSendCode.cshtml", + new TwoFactorSendCodeViewModel { - return View("NotFound"); - } + Token = code, + EmailSignature = _siteOptions.Value.Smtp.FromName, + MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() + }); - var tokenProvider = "Email"; - var code = await _userManager.GenerateTwoFactorTokenAsync(user, tokenProvider); - if (string.IsNullOrWhiteSpace(code)) + return RedirectToAction( + nameof(VerifyCode), + new { - return View("Error"); - } + Provider = tokenProvider, + ReturnUrl = returnUrl, + RememberMe = rememberMe + }); + } - await _emailSender.SendEmailAsync( - email: user.Email, - subject: "کد جدید اعتبارسنجی دو مرحله‌ای", - viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_TwoFactorSendCode.cshtml", - model: new TwoFactorSendCodeViewModel - { - Token = code, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() - }); - - return RedirectToAction( - nameof(VerifyCode), - new - { - Provider = tokenProvider, - ReturnUrl = returnUrl, - RememberMe = rememberMe - }); + [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(); + if (user == null) + { + return View("NotFound"); } - [AllowAnonymous] - [BreadCrumb(Title = "تائید کد", Order = 1)] - public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) + return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } + + [HttpPost, AllowAnonymous, ValidateAntiForgeryToken] + public async Task VerifyCode(VerifyCodeViewModel model) + { + if (model is null) { - // Require that the user has already logged in via username/password or external login - var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); - if (user == null) - { - return View("NotFound"); - } - return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + return View("Error"); } - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task VerifyCode(VerifyCodeViewModel model) + if (!ModelState.IsValid) { - if (!ModelState.IsValid) - { - return View(model); - } + return View(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); + // 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); - if (result.Succeeded) + if (result.Succeeded) + { + var returnUrl = model.ReturnUrl; + if (!string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl)) { - if (Url.IsLocalUrl(model.ReturnUrl)) - { - return Redirect(model.ReturnUrl); - } - return RedirectToAction(nameof(HomeController.Index), "Home"); + return Redirect(returnUrl); } - if (result.IsLockedOut) - { - _logger.LogWarning(7, "User account locked out."); - return View("Lockout"); - } + return RedirectToAction(nameof(HomeController.Index), "Home"); + } - ModelState.AddModelError(string.Empty, "کد وارد شده غیر معتبر است."); - return View(model); + if (result.IsLockedOut) + { + return View("Lockout"); } + + ModelState.AddModelError(string.Empty, "کد وارد شده غیر معتبر است."); + 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 85ded1f..642eb33 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserCardController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserCardController.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.Services.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; @@ -8,69 +6,67 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +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 : Controller + private readonly IApplicationRoleManager _roleManager; + private readonly IApplicationUserManager _userManager; + + public UserCardController( + IApplicationUserManager userManager, + IApplicationRoleManager roleManager) { - private readonly IApplicationUserManager _userManager; - private readonly IApplicationRoleManager _roleManager; + _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); + } - public UserCardController( - IApplicationUserManager userManager, - IApplicationRoleManager roleManager) + [BreadCrumb(Title = "ایندکس", Order = 1)] + public async Task Index(int? id) + { + if (!id.HasValue && User.Identity is { IsAuthenticated: true }) { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); + id = User.Identity.GetUserId(); } - [BreadCrumb(Title = "ایندکس", Order = 1)] - public async Task Index(int? id) + if (!id.HasValue) { - if (!id.HasValue && User.Identity.IsAuthenticated) - { - id = User.Identity.GetUserId(); - } - - if (!id.HasValue) - { - return View("Error"); - } - - var user = await _userManager.FindByIdIncludeUserRolesAsync(id.Value); - if (user == null) - { - return View("NotFound"); - } - - var model = new UserCardItemViewModel - { - User = user, - ShowAdminParts = User.IsInRole(ConstantRoles.Admin), - Roles = await _roleManager.GetAllCustomRolesAsync(), - ActiveTab = UserCardItemActiveTab.UserInfo - }; - return View(model); + return View("Error"); } - [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] - public async Task EmailToImage(int? id) + var user = await _userManager.FindByIdIncludeUserRolesAsync(id.Value); + if (user == null) { - if (!id.HasValue) - { - return NotFound(); - } - - var fileContents = await _userManager.GetEmailImageAsync(id); - return new FileContentResult(fileContents, "image/png"); + return View("NotFound"); } - [BreadCrumb(Title = "لیست کاربران آنلاین", Order = 1)] - public IActionResult OnlineUsers() + var model = new UserCardItemViewModel { - return View(); + User = user, + ShowAdminParts = User.IsInRole(ConstantRoles.Admin), + Roles = await _roleManager.GetAllCustomRolesAsync(), + ActiveTab = UserCardItemActiveTab.UserInfo + }; + return View(model); + } + + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + public async Task EmailToImage(int? id) + { + if (!id.HasValue) + { + return NotFound(); } + + var fileContents = await _userManager.GetEmailImageAsync(id); + return new FileContentResult(fileContents, "image/png"); + } + + [BreadCrumb(Title = "لیست کاربران آنلاین", Order = 1)] + public IActionResult OnlineUsers() + { + return 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 973029f..9f2112b 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserProfileController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UserProfileController.cs @@ -1,302 +1,305 @@ -using ASPNETCoreIdentitySample.Common.GuardToolkit; +using System.Runtime.Versioning; +using ASPNETCoreIdentitySample.Common.GuardToolkit; using ASPNETCoreIdentitySample.Common.IdentityToolkit; using ASPNETCoreIdentitySample.Entities.Identity; using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.Services.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity.Emails; using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; +using DNTCommon.Web.Core; using DNTPersianUtils.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.IO; -using System.Threading.Tasks; -using System; -using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers; + +[Authorize, Area(AreaConstants.IdentityArea), BreadCrumb(Title = "مشخصات کاربری", UseDefaultRouteUrl = true, Order = 0)] +public class UserProfileController : Controller { - [Authorize] - [Area(AreaConstants.IdentityArea)] - [BreadCrumb(Title = "مشخصات کاربری", UseDefaultRouteUrl = true, Order = 0)] - public class UserProfileController : 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) { - private readonly IEmailSender _emailSender; - 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; - private readonly ILogger _logger; - - public UserProfileController( - IApplicationUserManager userManager, - IApplicationRoleManager roleManager, - IApplicationSignInManager signInManager, - IProtectionProviderService protectionProviderService, - IUserValidator userValidator, - IUsedPasswordsService usedPasswordsService, - IUsersPhotoService usersPhotoService, - IOptionsSnapshot siteOptions, - IEmailSender emailSender, - ILogger logger) + if (!id.HasValue) { - _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)); + return View("Error"); } - [Authorize(Roles = ConstantRoles.Admin)] - [BreadCrumb(Title = "ایندکس", Order = 1)] - public async Task AdminEdit(int? id) - { - if (!id.HasValue) - { - return View("Error"); - } + var user = await _userManager.FindByIdAsync(id.Value.ToString(CultureInfo.InvariantCulture)); + return await RenderForm(user, true); + } - var user = await _userManager.FindByIdAsync(id.ToString()); - return await renderForm(user, isAdminEdit: true); - } + [BreadCrumb(Title = "ایندکس", Order = 1)] + public async Task Index() + { + var user = await _userManager.GetCurrentUserAsync(); + return await RenderForm(user, false); + } - [BreadCrumb(Title = "ایندکس", Order = 1)] - public async Task Index() + [SupportedOSPlatform("windows"), HttpPost, ValidateAntiForgeryToken] + public async Task Index(UserProfileViewModel model) + { + if (model is null) { - var user = await _userManager.GetCurrentUserAsync(); - return await renderForm(user, isAdminEdit: false); + return View("Error"); } - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Index(UserProfileViewModel model) + if (ModelState.IsValid) { - if (this.ModelState.IsValid) + var pid = _protectionProviderService.Decrypt(model.Pid); + if (string.IsNullOrWhiteSpace(pid)) { - var pid = _protectionProviderService.Decrypt(model.Pid); - if (string.IsNullOrWhiteSpace(pid)) - { - return View("Error"); - } + return View("Error"); + } - if (pid != _userManager.GetCurrentUserId() && - !_roleManager.IsCurrentUserInRole(ConstantRoles.Admin)) - { - _logger.LogWarning($"سعی در دسترسی غیرمجاز به ویرایش اطلاعات کاربر {pid}"); - return View("Error"); - } + if (!string.Equals(pid, _userManager.GetCurrentUserId(), StringComparison.Ordinal) && + !_roleManager.IsCurrentUserInRole(ConstantRoles.Admin)) + { + _logger.LogWarningMessage($"سعی در دسترسی غیرمجاز به ویرایش اطلاعات کاربر {pid}"); + return View("Error"); + } - var user = await _userManager.FindByIdAsync(pid); - if (user == null) - { - return View("NotFound"); - } + var user = await _userManager.FindByIdAsync(pid); + if (user == null) + { + return View("NotFound"); + } - user.FirstName = model.FirstName; - user.LastName = model.LastName; - user.IsEmailPublic = model.IsEmailPublic; - user.TwoFactorEnabled = model.TwoFactorEnabled; - user.Location = model.Location; + user.FirstName = model.FirstName; + user.LastName = model.LastName; + user.IsEmailPublic = model.IsEmailPublic; + user.TwoFactorEnabled = model.TwoFactorEnabled; + user.Location = model.Location; - updateUserBirthDate(model, user); + UpdateUserBirthDate(model, user); - if (!await updateUserName(model, user)) - { - return View(viewName: nameof(Index), model: model); - } + if (!await UpdateUserName(model, user)) + { + return View(nameof(Index), model); + } - if (!await updateUserAvatarImage(model, user)) - { - return View(viewName: nameof(Index), model: model); - } + if (!await UpdateUserAvatarImage(model, user)) + { + return View(nameof(Index), model); + } - if (!await updateUserEmail(model, user)) + if (!await UpdateUserEmail(model, user)) + { + return View(nameof(Index), model); + } + + var updateResult = await _userManager.UpdateAsync(user); + if (updateResult.Succeeded) + { + if (!model.IsAdminEdit) { - return View(viewName: nameof(Index), model: model); + // reflect the changes in the current user's Identity cookie + await _signInManager.RefreshSignInAsync(user); } - var updateResult = await _userManager.UpdateAsync(user); - if (updateResult.Succeeded) - { - if (!model.IsAdminEdit) + await _emailSender.SendEmailAsync( + user.Email, + "اطلاع رسانی به روز رسانی مشخصات کاربری", + "~/Areas/Identity/Views/EmailTemplates/_UserProfileUpdateNotification.cshtml", + new UserProfileUpdateNotificationViewModel { - // reflect the changes in the current user's Identity cookie - await _signInManager.RefreshSignInAsync(user); - } - - await _emailSender.SendEmailAsync( - email: user.Email, - subject: "اطلاع رسانی به روز رسانی مشخصات کاربری", - viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_UserProfileUpdateNotification.cshtml", - model: new UserProfileUpdateNotificationViewModel - { - User = user, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() - }); - - return RedirectToAction(nameof(Index), "UserCard", routeValues: new { id = user.Id }); - } + User = user, + EmailSignature = _siteOptions.Value.Smtp.FromName, + MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() + }); - ModelState.AddModelError("", updateResult.DumpErrors(useHtmlNewLine: true)); + return RedirectToAction(nameof(Index), "UserCard", new { id = user.Id }); } - return View(viewName: nameof(Index), model: model); + + ModelState.AddModelError("", updateResult.DumpErrors(true)); } - /// - /// For [Remote] validation - /// - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] - [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] - public async Task ValidateUsername(string username, string email, string pid) + return View(nameof(Index), model); + } + + /// + /// For [Remote] validation + /// + [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + public async Task ValidateUsername(string username, string email, string pid) + { + pid = _protectionProviderService.Decrypt(pid); + if (string.IsNullOrWhiteSpace(pid)) { - pid = _protectionProviderService.Decrypt(pid); - if (string.IsNullOrWhiteSpace(pid)) - { - return Json("اطلاعات وارد شده معتبر نیست."); - } + return Json("اطلاعات وارد شده معتبر نیست."); + } - var user = await _userManager.FindByIdAsync(pid); - user.UserName = username; - user.Email = email; + var user = await _userManager.FindByIdAsync(pid); + user.UserName = username; + user.Email = email; - var result = await _userValidator.ValidateAsync((UserManager)_userManager, user); - return Json(result.Succeeded ? "true" : result.DumpErrors(useHtmlNewLine: true)); - } + var result = await _userValidator.ValidateAsync((UserManager)_userManager, user); + return Json(result.Succeeded ? "true" : result.DumpErrors(true)); + } - private static void updateUserBirthDate(UserProfileViewModel model, User user) + private static void UpdateUserBirthDate(UserProfileViewModel model, User user) + { + if (model.DateOfBirthYear.HasValue && + model.DateOfBirthMonth.HasValue && + model.DateOfBirthDay.HasValue) { - if (model.DateOfBirthYear.HasValue && - model.DateOfBirthMonth.HasValue && - model.DateOfBirthDay.HasValue) - { - var date = - $"{model.DateOfBirthYear.Value.ToString()}/{model.DateOfBirthMonth.Value.ToString("00")}/{model.DateOfBirthDay.Value.ToString("00")}"; - user.BirthDate = date.ToGregorianDateTime(convertToUtc: true); - } - else - { - user.BirthDate = null; - } + var date = + $"{model.DateOfBirthYear.Value.ToString(CultureInfo.InvariantCulture)}/{model.DateOfBirthMonth.Value.ToString("00", CultureInfo.InvariantCulture)}/{model.DateOfBirthDay.Value.ToString("00", CultureInfo.InvariantCulture)}"; + user.BirthDate = date.ToGregorianDateTime(true); } - - private async Task renderForm(User user, bool isAdminEdit) + else { - _usersPhotoService.SetUserDefaultPhoto(user); + user.BirthDate = null; + } + } - var userProfile = new UserProfileViewModel - { - IsAdminEdit = isAdminEdit, - Email = user.Email, - PhotoFileName = user.PhotoFileName, - Location = user.Location, - UserName = user.UserName, - FirstName = user.FirstName, - LastName = user.LastName, - Pid = _protectionProviderService.Encrypt(user.Id.ToString()), - IsEmailPublic = user.IsEmailPublic, - TwoFactorEnabled = user.TwoFactorEnabled, - IsPasswordTooOld = await _usedPasswordsService.IsLastUserPasswordTooOldAsync(user.Id) - }; - - if (user.BirthDate.HasValue) - { - var pDateParts = user.BirthDate.Value.ToPersianYearMonthDay(); - userProfile.DateOfBirthYear = pDateParts.Year; - userProfile.DateOfBirthMonth = pDateParts.Month; - userProfile.DateOfBirthDay = pDateParts.Day; - } + private async Task RenderForm(User user, bool isAdminEdit) + { + _usersPhotoService.SetUserDefaultPhoto(user); - return View(viewName: nameof(Index), model: userProfile); + var userProfile = new UserProfileViewModel + { + IsAdminEdit = isAdminEdit, + Email = user.Email, + PhotoFileName = user.PhotoFileName, + Location = user.Location, + UserName = user.UserName, + FirstName = user.FirstName, + LastName = user.LastName, + Pid = _protectionProviderService.Encrypt(user.Id.ToString(CultureInfo.InvariantCulture)), + IsEmailPublic = user.IsEmailPublic, + TwoFactorEnabled = user.TwoFactorEnabled, + IsPasswordTooOld = await _usedPasswordsService.IsLastUserPasswordTooOldAsync(user.Id) + }; + + if (user.BirthDate.HasValue) + { + var pDateParts = user.BirthDate.Value.ToPersianYearMonthDay(); + userProfile.DateOfBirthYear = pDateParts.Year; + userProfile.DateOfBirthMonth = pDateParts.Month; + userProfile.DateOfBirthDay = pDateParts.Day; } - private async Task updateUserAvatarImage(UserProfileViewModel model, User user) - { - _usersPhotoService.SetUserDefaultPhoto(user); + return View(nameof(Index), userProfile); + } + + [SupportedOSPlatform("windows")] + private async Task UpdateUserAvatarImage(UserProfileViewModel model, User user) + { + _usersPhotoService.SetUserDefaultPhoto(user); - var photoFile = model.Photo; - if (photoFile?.Length > 0) + var photoFile = model.Photo; + if (photoFile?.Length > 0) + { + var imageOptions = _siteOptions.Value.UserAvatarImageOptions; + if (!photoFile.IsValidImageFile(imageOptions.MaxWidth, imageOptions.MaxHeight)) { - var imageOptions = _siteOptions.Value.UserAvatarImageOptions; - if (!photoFile.IsValidImageFile(maxWidth: imageOptions.MaxWidth, maxHeight: imageOptions.MaxHeight)) - { - this.ModelState.AddModelError("", - $"حداکثر اندازه تصویر قابل ارسال {imageOptions.MaxHeight} در {imageOptions.MaxWidth} پیکسل است"); - model.PhotoFileName = user.PhotoFileName; - return false; - } + ModelState.AddModelError("", + Invariant( + $"حداکثر اندازه تصویر قابل ارسال {imageOptions.MaxHeight} در {imageOptions.MaxWidth} پیکسل است")); + model.PhotoFileName = user.PhotoFileName; + return false; + } - var uploadsRootFolder = _usersPhotoService.GetUsersAvatarsFolderPath(); - var photoFileName = $"{user.Id}{Path.GetExtension(photoFile.FileName)}"; - var filePath = Path.Combine(uploadsRootFolder, photoFileName); - using (var fileStream = new FileStream(filePath, FileMode.Create)) - { - await photoFile.CopyToAsync(fileStream); - } - user.PhotoFileName = photoFileName; + var uploadsRootFolder = _usersPhotoService.GetUsersAvatarsFolderPath(); + var photoFileName = Invariant($"{user.Id}{Path.GetExtension(photoFile.FileName)}"); + var filePath = Path.Combine(uploadsRootFolder, photoFileName); + using (var fileStream = new FileStream(filePath, FileMode.Create)) + { + await photoFile.CopyToAsync(fileStream); } - return true; + + user.PhotoFileName = photoFileName; } - private async Task updateUserEmail(UserProfileViewModel model, User user) + return true; + } + + private async Task UpdateUserEmail(UserProfileViewModel model, User user) + { + if (!string.Equals(user.Email, model.Email, StringComparison.Ordinal)) { - if (user.Email != model.Email) + user.Email = model.Email; + var userValidator = + await _userValidator.ValidateAsync((UserManager)_userManager, user); + if (!userValidator.Succeeded) { - user.Email = model.Email; - var userValidator = - await _userValidator.ValidateAsync((UserManager)_userManager, user); - if (!userValidator.Succeeded) - { - ModelState.AddModelError("", userValidator.DumpErrors(useHtmlNewLine: true)); - return false; - } - - user.EmailConfirmed = false; - - var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); - await _emailSender.SendEmailAsync( - email: user.Email, - subject: "لطفا اکانت خود را تائید کنید", - viewNameOrPath: "~/Areas/Identity/Views/EmailTemplates/_RegisterEmailConfirmation.cshtml", - model: new RegisterEmailConfirmationViewModel - { - User = user, - EmailConfirmationToken = code, - EmailSignature = _siteOptions.Value.Smtp.FromName, - MessageDateTime = DateTime.UtcNow.ToLongPersianDateTimeString() - }); + ModelState.AddModelError("", userValidator.DumpErrors(true)); + return false; } - return true; + user.EmailConfirmed = false; + + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + 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() + }); } - private async Task updateUserName(UserProfileViewModel model, User user) + return true; + } + + private async Task UpdateUserName(UserProfileViewModel model, User user) + { + if (!string.Equals(user.UserName, model.UserName, StringComparison.Ordinal)) { - if (user.UserName != model.UserName) + user.UserName = model.UserName; + var userValidator = + await _userValidator.ValidateAsync((UserManager)_userManager, user); + if (!userValidator.Succeeded) { - user.UserName = model.UserName; - var userValidator = - await _userValidator.ValidateAsync((UserManager)_userManager, user); - if (!userValidator.Succeeded) - { - ModelState.AddModelError("", userValidator.DumpErrors(useHtmlNewLine: true)); - return false; - } + ModelState.AddModelError("", userValidator.DumpErrors(true)); + return false; } - return true; } + + return true; } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UsersManagerController.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UsersManagerController.cs index bcbc813..a7220ba 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UsersManagerController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Controllers/UsersManagerController.cs @@ -7,187 +7,182 @@ using DNTCommon.Web.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using System; -using System.Threading.Tasks; -namespace ASPNETCoreIdentitySample.Areas.Identity.Controllers +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 : Controller - { - private const int DefaultPageSize = 7; + private const int DefaultPageSize = 7; - private readonly IApplicationRoleManager _roleManager; - private readonly IApplicationUserManager _userManager; + private readonly IApplicationRoleManager _roleManager; + private readonly IApplicationUserManager _userManager; - public UsersManagerController( - IApplicationUserManager userManager, - IApplicationRoleManager roleManager) + 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)] + public async Task ActivateUserEmailStat(int userId) + { + User thisUser = null; + var result = await _userManager.UpdateUserAndSecurityStampAsync( + userId, user => + { + user.EmailConfirmed = true; + thisUser = user; + }); + if (!result.Succeeded) { - _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); - _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); + return BadRequest(result.DumpErrors(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; - }); - if (!result.Succeeded) - { - return BadRequest(error: result.DumpErrors(useHtmlNewLine: true)); - } + return await ReturnUserCardPartialView(thisUser); + } - return await returnUserCardPartialView(thisUser); + [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; + }); + if (!result.Succeeded) + { + return BadRequest(result.DumpErrors(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; - }); - if (!result.Succeeded) - { - return BadRequest(error: result.DumpErrors(useHtmlNewLine: true)); - } + return await ReturnUserCardPartialView(thisUser); + } - return await returnUserCardPartialView(thisUser); + [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); + if (!result.Succeeded) + { + return BadRequest(result.DumpErrors(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); - if (!result.Succeeded) - { - return BadRequest(error: result.DumpErrors(useHtmlNewLine: true)); - } + return await ReturnUserCardPartialView(thisUser); + } - return await returnUserCardPartialView(thisUser); + [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; + }); + if (!result.Succeeded) + { + return BadRequest(result.DumpErrors(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; - }); - if (!result.Succeeded) - { - return BadRequest(error: result.DumpErrors(useHtmlNewLine: true)); - } + return await ReturnUserCardPartialView(thisUser); + } - return await returnUserCardPartialView(thisUser); + [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; + }); + if (!result.Succeeded) + { + return BadRequest(result.DumpErrors(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; - }); - if (!result.Succeeded) - { - return BadRequest(error: result.DumpErrors(useHtmlNewLine: true)); - } + return await ReturnUserCardPartialView(thisUser); + } - return await returnUserCardPartialView(thisUser); + [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; + }); + if (!result.Succeeded) + { + return BadRequest(result.DumpErrors(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; - }); - if (!result.Succeeded) - { - return BadRequest(error: result.DumpErrors(useHtmlNewLine: true)); - } + return await ReturnUserCardPartialView(thisUser); + } - return await returnUserCardPartialView(thisUser); + [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); + + model.Paging.CurrentPage = page.Value; + model.Paging.ItemsPerPage = DefaultPageSize; + model.Paging.ShowFirstLast = true; + + if (HttpContext.Request.IsAjaxRequest()) + { + return PartialView("_UsersList", model); } - [BreadCrumb(Title = "ایندکس", Order = 1)] - public async Task Index(int? page = 1, string field = "Id", SortOrder order = SortOrder.Ascending) + return View(model); + } + + [AjaxOnly, HttpPost, ValidateAntiForgeryToken, ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + public async Task SearchUsers(SearchUsersViewModel model) + { + if (model is null) { - var model = await _userManager.GetPagedUsersListAsync( - pageNumber: page.Value - 1, - recordsPerPage: DefaultPageSize, - sortByField: field, - sortOrder: order, - showAllUsers: true); - - model.Paging.CurrentPage = page.Value; - model.Paging.ItemsPerPage = DefaultPageSize; - model.Paging.ShowFirstLast = true; - - if (HttpContext.Request.IsAjaxRequest()) - { - return PartialView("_UsersList", model); - } - return View(model); + return BadRequest(); } - [AjaxOnly, HttpPost, ValidateAntiForgeryToken] - [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] - public async Task SearchUsers(SearchUsersViewModel model) - { - var pagedUsersList = await _userManager.GetPagedUsersListAsync( - model: model, - pageNumber: 0); + var pagedUsersList = await _userManager.GetPagedUsersListAsync( + model, + 0); - pagedUsersList.Paging.CurrentPage = 1; - pagedUsersList.Paging.ItemsPerPage = model.MaxNumberOfRows; - pagedUsersList.Paging.ShowFirstLast = true; + pagedUsersList.Paging.CurrentPage = 1; + pagedUsersList.Paging.ItemsPerPage = model.MaxNumberOfRows; + pagedUsersList.Paging.ShowFirstLast = true; - model.PagedUsersList = pagedUsersList; - return PartialView("_SearchUsers", model); - } + model.PagedUsersList = pagedUsersList; + return PartialView("_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 - }); - } + 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 + }); } } \ 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 1d9af18..135d528 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/SecurityTrimmingTagHelper.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/SecurityTrimmingTagHelper.cs @@ -1,68 +1,73 @@ -using System; -using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.Services.Contracts.Identity; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; -namespace ASPNETCoreIdentitySample.Areas.Identity.TagHelpers +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 { - /// - /// More info: http://www.dotnettips.info/post/2527 - /// And http://www.dotnettips.info/post/2581 - /// - [HtmlTargetElement("security-trimming")] - public class SecurityTrimmingTagHelper : TagHelper - { - private readonly ISecurityTrimmingService _securityTrimmingService; + private readonly ISecurityTrimmingService _securityTrimmingService; - public SecurityTrimmingTagHelper(ISecurityTrimmingService securityTrimmingService) - { - _securityTrimmingService = securityTrimmingService ?? throw new ArgumentNullException(nameof(securityTrimmingService)); - } + public SecurityTrimmingTagHelper(ISecurityTrimmingService securityTrimmingService) + { + _securityTrimmingService = + securityTrimmingService ?? throw new ArgumentNullException(nameof(securityTrimmingService)); + } - /// - /// The name of the action method. - /// - [HtmlAttributeName("asp-action")] - public string Action { get; set; } + /// + /// The name of the action method. + /// + [HtmlAttributeName("asp-action")] + public string Action { get; set; } - /// - /// The name of the area. - /// - [HtmlAttributeName("asp-area")] - public string Area { get; set; } + /// + /// The name of the area. + /// + [HtmlAttributeName("asp-area")] + public string Area { get; set; } - /// - /// The name of the controller. - /// - [HtmlAttributeName("asp-controller")] - public string Controller { get; set; } + /// + /// The name of the controller. + /// + [HtmlAttributeName("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) + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) { - if (context == null) throw new ArgumentNullException(nameof(context)); - if (output == null) throw new ArgumentNullException(nameof(output)); - - // don't render the tag. - output.TagName = null; + throw new ArgumentNullException(nameof(context)); + } - if (!ViewContext.HttpContext.User.Identity.IsAuthenticated) - { - // suppress the output and generate nothing. - output.SuppressOutput(); - } + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } - if (_securityTrimmingService.CanCurrentUserAccess(Area, Controller, Action)) - { - // fine, do nothing. - return; - } + // don't render the tag. + output.TagName = null; - // else, suppress the output and generate nothing. + if (ViewContext.HttpContext.User.Identity is null || !ViewContext.HttpContext.User.Identity.IsAuthenticated) + { + // suppress the output and generate nothing. output.SuppressOutput(); } + + if (_securityTrimmingService.CanCurrentUserAccess(Area, Controller, Action)) + { + // fine, do nothing. + return; + } + + // else, suppress the output and generate nothing. + output.SuppressOutput(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/VisibilityTagHelper.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/VisibilityTagHelper.cs index cdacb04..8a58abd 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/VisibilityTagHelper.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/TagHelpers/VisibilityTagHelper.cs @@ -1,34 +1,38 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.AspNetCore.Razor.TagHelpers; -namespace ASPNETCoreIdentitySample.Areas.Identity.TagHelpers +namespace ASPNETCoreIdentitySample.Areas.Identity.TagHelpers; + +/// +/// More info: http://www.dntips.ir/post/2527 +/// And http://www.dntips.ir/post/2581 +/// +[HtmlTargetElement("div")] +public class VisibilityTagHelper : TagHelper { /// - /// More info: http://www.dotnettips.info/post/2527 - /// And http://www.dotnettips.info/post/2581 + /// default to true otherwise all existing target elements will not be shown, because bool's default to false /// - [HtmlTargetElement("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")] - public bool IsVisible { get; set; } = true; + [HtmlAttributeName("asp-is-visible")] + public bool IsVisible { get; set; } = true; - public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (context == null) { - if (context == null) throw new ArgumentNullException(nameof(context)); - if (output == null) throw new ArgumentNullException(nameof(output)); + throw new ArgumentNullException(nameof(context)); + } - if (!IsVisible) - { - // suppress the output and generate nothing. - output.SuppressOutput(); - } + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } - return base.ProcessAsync(context, output); + if (!IsVisible) + { + // suppress the output and generate nothing. + output.SuppressOutput(); } + + return base.ProcessAsync(context, output); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/OnlineUsersViewComponent.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/OnlineUsersViewComponent.cs index b0e600c..20dd18a 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/OnlineUsersViewComponent.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/OnlineUsersViewComponent.cs @@ -1,30 +1,28 @@ -using System.Threading.Tasks; -using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.Areas.Identity.ViewComponents +namespace ASPNETCoreIdentitySample.Areas.Identity.ViewComponents; + +public class OnlineUsersViewComponent : ViewComponent { - public class OnlineUsersViewComponent : ViewComponent - { - private readonly ISiteStatService _siteStatService; + private readonly ISiteStatService _siteStatService; - public OnlineUsersViewComponent(ISiteStatService siteStatService) - { - _siteStatService = 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(viewName: "~/Areas/Identity/Views/Shared/Components/OnlineUsers/Default.cshtml", - model: new OnlineUsersViewModel - { - MinutesToTake = minutesToTake, - NumbersToTake = numbersToTake, - ShowMoreItemsLink = showMoreItemsLink, - Users = usersList - }); - } + 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", + new OnlineUsersViewModel + { + MinutesToTake = minutesToTake, + NumbersToTake = numbersToTake, + ShowMoreItemsLink = showMoreItemsLink, + Users = usersList + }); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/TodayBirthDaysViewComponent.cs b/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/TodayBirthDaysViewComponent.cs index 07ed5a9..65527eb 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/TodayBirthDaysViewComponent.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/ViewComponents/TodayBirthDaysViewComponent.cs @@ -1,30 +1,28 @@ -using System.Threading.Tasks; -using ASPNETCoreIdentitySample.Services.Contracts.Identity; +using ASPNETCoreIdentitySample.Services.Contracts.Identity; using ASPNETCoreIdentitySample.ViewModels.Identity; using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.Areas.Identity.ViewComponents +namespace ASPNETCoreIdentitySample.Areas.Identity.ViewComponents; + +public class TodayBirthDaysViewComponent : ViewComponent { - public class TodayBirthDaysViewComponent : ViewComponent - { - private readonly ISiteStatService _siteStatService; + private readonly ISiteStatService _siteStatService; - public TodayBirthDaysViewComponent(ISiteStatService siteStatService) - { - _siteStatService = siteStatService; - } + public TodayBirthDaysViewComponent(ISiteStatService siteStatService) + { + _siteStatService = siteStatService; + } - public async Task InvokeAsync() - { - var usersList = await _siteStatService.GetTodayBirthdayListAsync(); - var usersAverageAge = await _siteStatService.GetUsersAverageAge(); + public async Task InvokeAsync() + { + var usersList = await _siteStatService.GetTodayBirthdayListAsync(); + var usersAverageAge = await _siteStatService.GetUsersAverageAge(); - return View(viewName: "~/Areas/Identity/Views/Shared/Components/TodayBirthDays/Default.cshtml", - model: new TodayBirthDaysViewModel - { - Users = usersList, - AgeStat = usersAverageAge - }); - } + return View("~/Areas/Identity/Views/Shared/Components/TodayBirthDays/Default.cshtml", + new TodayBirthDaysViewModel + { + Users = usersList, + AgeStat = usersAverageAge + }); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Identity/Views/RolesManager/UsersInRole.cshtml b/src/ASPNETCoreIdentitySample/Areas/Identity/Views/RolesManager/UsersInRole.cshtml index 8e770b8..8e04a9a 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Identity/Views/RolesManager/UsersInRole.cshtml +++ b/src/ASPNETCoreIdentitySample/Areas/Identity/Views/RolesManager/UsersInRole.cshtml @@ -1,5 +1,4 @@ -@using Microsoft.AspNetCore.Routing -@model PagedUsersListViewModel +@model PagedUsersListViewModel @{ var roleId = (string)this.Context.GetRouteValue("Id"); diff --git a/src/ASPNETCoreIdentitySample/Areas/Test/Controllers/DynamicPermissionsAreaSampleController.cs b/src/ASPNETCoreIdentitySample/Areas/Test/Controllers/DynamicPermissionsAreaSampleController.cs index 7cb4978..7715084 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Test/Controllers/DynamicPermissionsAreaSampleController.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Test/Controllers/DynamicPermissionsAreaSampleController.cs @@ -1,27 +1,22 @@ using System.ComponentModel; using ASPNETCoreIdentitySample.Services.Identity; -using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using ASPNETCoreIdentitySample.Areas.Test; -namespace ASPNETCoreIdentitySample.Areas.Test.Controllers +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("کنترلر نمونه با سطح دسترسی پویا در يك ناحيه خاص آزمايشي")] +public class DynamicPermissionsAreaSampleController : Controller { - /// - /// More info: http://www.dotnettips.info/post/2581 - /// - [Authorize(Policy = ConstantPolicies.DynamicPermission)] - [Area(TestAreaConstants.TestArea)] - [BreadCrumb(UseDefaultRouteUrl = true, Order = 0)] - [DisplayName("کنترلر نمونه با سطح دسترسی پویا در يك ناحيه خاص آزمايشي")] - public class DynamicPermissionsAreaSampleController : Controller + [DisplayName("ایندکس"), BreadCrumb(Order = 1)] + public IActionResult Index() { - [DisplayName("ایندکس")] - [BreadCrumb(Order = 1)] - public IActionResult Index() - { - return View(); - } + return View(); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Areas/Test/TestAreaConstants.cs b/src/ASPNETCoreIdentitySample/Areas/Test/TestAreaConstants.cs index 010685e..e2bc468 100644 --- a/src/ASPNETCoreIdentitySample/Areas/Test/TestAreaConstants.cs +++ b/src/ASPNETCoreIdentitySample/Areas/Test/TestAreaConstants.cs @@ -1,10 +1,9 @@ -namespace ASPNETCoreIdentitySample.Areas.Test +namespace ASPNETCoreIdentitySample.Areas.Test; + +/// +/// More info: http://www.dntips.ir/post/2550 +/// +public static class TestAreaConstants { - /// - /// More info: http://www.dotnettips.info/post/2550 - /// - public static class TestAreaConstants - { - public const string TestArea = "Test"; - } + public const string TestArea = "Test"; } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsSampleController.cs b/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsSampleController.cs index 294be9b..3e8ac0c 100644 --- a/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsSampleController.cs +++ b/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsSampleController.cs @@ -5,55 +5,48 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace ASPNETCoreIdentitySample.Controllers +namespace ASPNETCoreIdentitySample.Controllers; + +/// +/// More info: http://www.dntips.ir/post/2581 +/// +[Authorize(Policy = ConstantPolicies.DynamicPermission), BreadCrumb(UseDefaultRouteUrl = true, Order = 0), + DisplayName("کنترلر نمونه با سطح دسترسی پویا")] +public class DynamicPermissionsSampleController : Controller { - /// - /// More info: http://www.dotnettips.info/post/2581 - /// - [Authorize(Policy = ConstantPolicies.DynamicPermission)] - [BreadCrumb(UseDefaultRouteUrl = true, Order = 0)] - [DisplayName("کنترلر نمونه با سطح دسترسی پویا")] - public class DynamicPermissionsSampleController : Controller + [DisplayName("ایندکس"), BreadCrumb(Order = 1)] + public IActionResult Index() { - [DisplayName("ایندکس")] - [BreadCrumb(Order = 1)] - public IActionResult Index() - { - return View(); - } + return View(); + } - [HttpPost, ValidateAntiForgeryToken] - public IActionResult Index(RoleViewModel model) - { - return View(model); - } + [HttpPost, ValidateAntiForgeryToken] + public IActionResult Index(RoleViewModel model) + { + return View(model); + } - [DisplayName("گزارش از لیست کتاب‌ها")] - [BreadCrumb(Order = 1)] - public IActionResult Books() - { - return View(viewName: "Index"); - } + [DisplayName("گزارش از لیست کتاب‌ها"), BreadCrumb(Order = 1)] + public IActionResult Books() + { + return View("Index"); + } - [DisplayName("گزارش از لیست مراجعان")] - [BreadCrumb(Order = 1)] - public IActionResult Users() - { - return View(viewName: "Index"); - } + [DisplayName("گزارش از لیست مراجعان"), BreadCrumb(Order = 1)] + public IActionResult Users() + { + return View("Index"); + } - [DisplayName("گزارش از لیست امانات")] - [BreadCrumb(Order = 1)] - public IActionResult BooksGiven() - { - return View(viewName: "Index"); - } + [DisplayName("گزارش از لیست امانات"), BreadCrumb(Order = 1)] + public IActionResult BooksGiven() + { + return View("Index"); + } - [DisplayName("گزارش از لیست مفقودی‌ها")] - [BreadCrumb(Order = 1)] - public IActionResult BooksMissings() - { - return View(viewName: "Index"); - } + [DisplayName("گزارش از لیست مفقودی‌ها"), BreadCrumb(Order = 1)] + public IActionResult BooksMissings() + { + return View("Index"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsTestController.cs b/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsTestController.cs index 1264833..c91c457 100644 --- a/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsTestController.cs +++ b/src/ASPNETCoreIdentitySample/Controllers/DynamicPermissionsTestController.cs @@ -1,60 +1,53 @@ -using ASPNETCoreIdentitySample.Services.Identity; +using System.ComponentModel; +using ASPNETCoreIdentitySample.Services.Identity; +using ASPNETCoreIdentitySample.ViewModels.Identity; using DNTBreadCrumb.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using System.ComponentModel; -using ASPNETCoreIdentitySample.ViewModels.Identity; -namespace ASPNETCoreIdentitySample.Controllers +namespace ASPNETCoreIdentitySample.Controllers; + +/// +/// More info: http://www.dntips.ir/post/2581 +/// +[Authorize(Policy = ConstantPolicies.DynamicPermission), BreadCrumb(UseDefaultRouteUrl = true, Order = 0), + DisplayName("کنترلر آزمایشی با سطح دسترسی پویا")] +// [NoBrowserCache] +public class DynamicPermissionsTestController : Controller { - /// - /// More info: http://www.dotnettips.info/post/2581 - /// - [Authorize(Policy = ConstantPolicies.DynamicPermission)] - [BreadCrumb(UseDefaultRouteUrl = true, Order = 0)] - [DisplayName("کنترلر آزمایشی با سطح دسترسی پویا")] - // [NoBrowserCache] - public class DynamicPermissionsTestController : Controller + [DisplayName("ایندکس"), BreadCrumb(Order = 1)] + public IActionResult Index() { - [DisplayName("ایندکس")] - [BreadCrumb(Order = 1)] - public IActionResult Index() - { - return View(); - } + return View(); + } - [HttpPost] // More info: http://www.dotnettips.info/post/2468/ and http://www.dotnettips.info/post/2470/ - public IActionResult Index([FromBody]RoleViewModel model) - { - return Json(model); - } + [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); + } - [DisplayName("گزارش از لیست محصولات")] - [BreadCrumb(Order = 1)] - public IActionResult Products() - { - return View(viewName: "Index"); - } + [DisplayName("گزارش از لیست محصولات"), BreadCrumb(Order = 1)] + public IActionResult Products() + { + return View("Index"); + } - [DisplayName("گزارش از لیست سفارشات")] - [BreadCrumb(Order = 1)] - public IActionResult Orders() - { - return View(viewName: "Index"); - } + [DisplayName("گزارش از لیست سفارشات"), BreadCrumb(Order = 1)] + public IActionResult Orders() + { + return View("Index"); + } - [DisplayName("گزارش از لیست فروش")] - [BreadCrumb(Order = 1)] - public IActionResult Sells() - { - return View(viewName: "Index"); - } + [DisplayName("گزارش از لیست فروش"), BreadCrumb(Order = 1)] + public IActionResult Sells() + { + return View("Index"); + } - [DisplayName("گزارش از لیست خریداران")] - [BreadCrumb(Order = 1)] - public IActionResult Customers() - { - return View(viewName: "Index"); - } + [DisplayName("گزارش از لیست خریداران"), BreadCrumb(Order = 1)] + public IActionResult Customers() + { + return View("Index"); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Controllers/ErrorController.cs b/src/ASPNETCoreIdentitySample/Controllers/ErrorController.cs index 2156039..1ecfcc0 100644 --- a/src/ASPNETCoreIdentitySample/Controllers/ErrorController.cs +++ b/src/ASPNETCoreIdentitySample/Controllers/ErrorController.cs @@ -1,62 +1,65 @@ -using DNTBreadCrumb.Core; +using System.Text; +using DNTBreadCrumb.Core; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System.Text; -namespace ASPNETCoreIdentitySample.Controllers +namespace ASPNETCoreIdentitySample.Controllers; + +[BreadCrumb(Title = "خطا", UseDefaultRouteUrl = true, Order = 0, GlyphIcon = "fas fa-warning")] +public class ErrorController : Controller { - [BreadCrumb(Title = "خطا", UseDefaultRouteUrl = true, Order = 0, GlyphIcon = "fas fa-warning")] - public class ErrorController : Controller + private readonly ILogger _logger; + + public ErrorController(ILogger logger) { - private readonly ILogger _logger; + _logger = logger; + } + + /// + /// More info: http://www.dntips.ir/post/2446 + /// + [BreadCrumb(Title = "ایندکس", Order = 2, GlyphIcon = "fas fa-navicon")] + 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"); + + var exceptionHandlerFeature = HttpContext.Features.Get(); + if (exceptionHandlerFeature?.Error != null) + { + var exception = exceptionHandlerFeature.Error; + logBuilder.Append("

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

") + .AppendLine(exception.StackTrace); + } - public ErrorController(ILogger logger) + foreach (var header in Request.Headers) { - _logger = logger; + var headerValues = header.Value.ToString(); + logBuilder.Append(header.Key).Append(": ").AppendLine(headerValues); } - /// - /// More info: http://www.dotnettips.info/post/2446 - /// - [BreadCrumb(Title = "ایندکس", Order = 2, GlyphIcon = "fas fa-navicon")] - public IActionResult Index(int? id) + _logger.LogErrorMessage(logBuilder.ToString()); + + if (id == null) { - 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"); - - var exceptionHandlerFeature = this.HttpContext.Features.Get(); - if (exceptionHandlerFeature?.Error != null) - { - var exception = exceptionHandlerFeature.Error; - logBuilder.Append("

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

").AppendLine(exception.StackTrace); - } - - foreach (var header in Request.Headers) - { - var headerValues = string.Join(",", value: header.Value); - logBuilder.Append(header.Key).Append(": ").AppendLine(headerValues); - } - _logger.LogError(logBuilder.ToString()); - - if (id == null) - { + return View("Error"); + } + + switch (id.Value) + { + case 401: + case 403: + return View("AccessDenied"); + case 404: + return View("NotFound"); + + default: return View("Error"); - } - - switch (id.Value) - { - case 401: - case 403: - return View("AccessDenied"); - case 404: - return View("NotFound"); - - default: - return View("Error"); - } } } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Controllers/HomeController.cs b/src/ASPNETCoreIdentitySample/Controllers/HomeController.cs index 2c1d992..f1f738d 100644 --- a/src/ASPNETCoreIdentitySample/Controllers/HomeController.cs +++ b/src/ASPNETCoreIdentitySample/Controllers/HomeController.cs @@ -1,34 +1,33 @@ using DNTBreadCrumb.Core; +using DNTCommon.Web.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample.Controllers +namespace ASPNETCoreIdentitySample.Controllers; + +[BreadCrumb(Title = "خانه", UseDefaultRouteUrl = true, Order = 0)] +public class HomeController : Controller { - [BreadCrumb(Title = "خانه", UseDefaultRouteUrl = true, Order = 0)] - public class HomeController : Controller + [BreadCrumb(Title = "ایندکس", Order = 1)] + public IActionResult Index() { - [BreadCrumb(Title = "ایندکس", Order = 1)] - public IActionResult Index() - { - return View(); - } + return View(); + } - [BreadCrumb(Title = "خطا", Order = 1)] - public IActionResult Error() - { - return View(); - } + [BreadCrumb(Title = "خطا", Order = 1)] + public IActionResult Error() + { + return View(); + } - /// - /// To test automatic challenge after redirecting from another site - /// Sample URL: http://localhost:5000/Home/CallBackResult?token=1&status=2&orderId=3&terminalNo=4&rrn=5 - /// - [Authorize] - 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 }); - } + /// + /// To test automatic challenge after redirecting from another site + /// Sample URL: http://localhost:5000/Home/CallBackResult?token=1&status=2&orderId=3&terminalNo=4&rrn=5 + /// + [Authorize] + 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 }); } } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Program.cs b/src/ASPNETCoreIdentitySample/Program.cs index ce45401..992dc75 100644 --- a/src/ASPNETCoreIdentitySample/Program.cs +++ b/src/ASPNETCoreIdentitySample/Program.cs @@ -1,39 +1,99 @@ -using Microsoft.AspNetCore.Hosting; +using ASPNETCoreIdentitySample.IocConfig; using ASPNETCoreIdentitySample.Services.Identity.Logger; -using ASPNETCoreIdentitySample.IocConfig; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; +using DNTCaptcha.Core; +using DNTCommon.Web.Core; -namespace ASPNETCoreIdentitySample +var builder = WebApplication.CreateBuilder(args); +ConfigureLogging(builder.Logging, builder.Environment, builder.Configuration); +ConfigureServices(builder.Services, builder.Configuration); +var webApp = builder.Build(); +ConfigureMiddlewares(webApp, webApp.Environment); +ConfigureEndpoints(webApp); +ConfigureDatabase(webApp); +webApp.Run(); + +void ConfigureServices(IServiceCollection services, IConfiguration configuration) +{ + services.Configure(options => configuration.Bind(options)); + services.Configure(options => + configuration.GetSection("ContentSecurityPolicyConfig").Bind(options)); + + // Adds all of the ASP.NET Core Identity related services and configurations at once. + services.AddCustomIdentityServices(configuration); + + services.AddMvc(options => options.UseYeKeModelBinder()); + + services.AddDNTCommonWeb(); + services.AddDNTCaptcha(options => + { + options.UseCookieStorageProvider() + .AbsoluteExpiration(7) + .ShowThousandsSeparators(false) + .WithNoise(25, 3) + .WithEncryptionKey("This is my secure key!"); + }); + services.AddCloudscribePagination(); + + services.AddControllersWithViews(options => { options.Filters.Add(typeof(ApplyCorrectYeKeFilterAttribute)); }); + services.AddRazorPages(); +} + +void ConfigureLogging(ILoggingBuilder logging, IHostEnvironment env, IConfiguration configuration) +{ + logging.ClearProviders(); + + logging.AddDebug(); + + if (env.IsDevelopment()) + { + logging.AddConsole(); + } + + logging.AddDbLogger(); // You can change its Log Level using the `appsettings.json` file -> Logging -> LogLevel -> Default + logging.AddConfiguration(configuration.GetSection("Logging")); +} + +void ConfigureMiddlewares(IApplicationBuilder app, IHostEnvironment env) { - public class Program + if (!env.IsDevelopment()) { - public static void Main(string[] args) - { - var host = CreateHostBuilder(args).Build(); - host.Services.InitializeDb(); - host.Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.ConfigureLogging((hostingContext, logging) => - { - logging.ClearProviders(); - - logging.AddDebug(); - - if (hostingContext.HostingEnvironment.IsDevelopment()) - { - logging.AddConsole(); - } - - logging.AddDbLogger(); // You can change its Log Level using the `appsettings.json` file -> Logging -> LogLevel -> Default - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - }) - .UseStartup(); - }); + app.UseHsts(); } + + app.UseHttpsRedirection(); + app.UseExceptionHandler("/error/index/500"); + app.UseStatusCodePagesWithReExecute("/error/index/{0}"); + + app.UseContentSecurityPolicy(); + + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); +} + +void ConfigureEndpoints(IApplicationBuilder app) +{ + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + + endpoints.MapControllerRoute( + "areaRoute", + "{area:exists}/{controller=Account}/{action=Index}/{id?}"); + + endpoints.MapControllerRoute( + "default", + "{controller=Home}/{action=Index}/{id?}"); + + endpoints.MapRazorPages(); + }); +} + +void ConfigureDatabase(IApplicationBuilder app) +{ + app.ApplicationServices.InitializeDb(); } \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Startup.cs b/src/ASPNETCoreIdentitySample/Startup.cs deleted file mode 100644 index e100d9d..0000000 --- a/src/ASPNETCoreIdentitySample/Startup.cs +++ /dev/null @@ -1,85 +0,0 @@ -using ASPNETCoreIdentitySample.ViewModels.Identity.Settings; -using DNTCaptcha.Core; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using ASPNETCoreIdentitySample.IocConfig; -using DNTCommon.Web.Core; -using ASPNETCoreIdentitySample.Common.WebToolkit; -using Microsoft.Extensions.Hosting; -using Microsoft.AspNetCore.Http; - -namespace ASPNETCoreIdentitySample -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - services.Configure(options => Configuration.Bind(options)); - services.Configure(options => Configuration.GetSection("ContentSecurityPolicyConfig").Bind(options)); - - // Adds all of the ASP.NET Core Identity related services and configurations at once. - services.AddCustomIdentityServices(); - - services.AddMvc(options => options.UseYeKeModelBinder()); - - services.AddDNTCommonWeb(); - services.AddDNTCaptcha(options => - { - options.UseCookieStorageProvider(SameSiteMode.Strict) - .AbsoluteExpiration(minutes: 7) - .ShowThousandsSeparators(false) - .WithNoise(pixelsDensity: 25, linesCount: 3) - .WithEncryptionKey("This is my secure key!"); - }); - services.AddCloudscribePagination(); - - services.AddControllersWithViews(); - services.AddRazorPages(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (!env.IsDevelopment()) - { - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseExceptionHandler("/error/index/500"); - app.UseStatusCodePagesWithReExecute("/error/index/{0}"); - - app.UseContentSecurityPolicy(); - - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - - endpoints.MapControllerRoute( - name: "areaRoute", - pattern: "{area:exists}/{controller=Account}/{action=Index}/{id?}"); - - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - - endpoints.MapRazorPages(); - }); - } - } -} \ No newline at end of file diff --git a/src/ASPNETCoreIdentitySample/Views/Shared/_Layout.cshtml b/src/ASPNETCoreIdentitySample/Views/Shared/_Layout.cshtml index 2564b4c..92ba6e6 100644 --- a/src/ASPNETCoreIdentitySample/Views/Shared/_Layout.cshtml +++ b/src/ASPNETCoreIdentitySample/Views/Shared/_Layout.cshtml @@ -4,87 +4,88 @@ - - - + + + @pageTitle - + -