diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicOptionsManagerExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicOptionsManagerExtensions.cs new file mode 100644 index 0000000000..5562555c89 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicOptionsManagerExtensions.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Volo.Abp.Options; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ServiceCollectionDynamicOptionsManagerExtensions + { + public static IServiceCollection AddAbpDynamicOptions(this IServiceCollection services) + where TOptions : class + where TManager : AbpDynamicOptionsManager + { + services.Replace(ServiceDescriptor.Scoped(typeof(IOptions), typeof(TManager))); + services.Replace(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot), typeof(TManager))); + + return services; + } + } +} diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Options/OptionsAbpDynamicOptionsManagerExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Options/OptionsAbpDynamicOptionsManagerExtensions.cs new file mode 100644 index 0000000000..d7c686b24a --- /dev/null +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Options/OptionsAbpDynamicOptionsManagerExtensions.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Options; + +namespace Microsoft.Extensions.Options +{ + public static class OptionsAbpDynamicOptionsManagerExtensions + { + public static Task SetAsync(this IOptions options) + where T : class + { + return options.ToDynamicOptions().SetAsync(); + } + + public static Task SetAsync(this IOptions options, string name) + where T : class + { + return options.ToDynamicOptions().SetAsync(name); + } + + private static AbpDynamicOptionsManager ToDynamicOptions(this IOptions options) + where T : class + { + if (options is AbpDynamicOptionsManager dynamicOptionsManager) + { + return dynamicOptionsManager; + } + + throw new AbpException($"Options must be derived from the {typeof(AbpDynamicOptionsManager<>).FullName}!"); + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpDynamicOptionsManager.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpDynamicOptionsManager.cs new file mode 100644 index 0000000000..af40ff1825 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpDynamicOptionsManager.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.Options +{ + public abstract class AbpDynamicOptionsManager : OptionsManager + where T : class + { + protected AbpDynamicOptionsManager(IOptionsFactory factory) + : base(factory) + { + + } + + public Task SetAsync() => SetAsync(Microsoft.Extensions.Options.Options.DefaultName); + + public virtual Task SetAsync(string name) + { + return OverrideOptionsAsync(base.Get(name)); + } + + protected abstract Task OverrideOptionsAsync(T options); + } +} diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AccountAppService.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AccountAppService.cs index ac3c6c2735..8fb043c6a9 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AccountAppService.cs +++ b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AccountAppService.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; using Volo.Abp.Account.Emailing; using Volo.Abp.Account.Localization; using Volo.Abp.Account.Settings; @@ -16,16 +17,21 @@ namespace Volo.Abp.Account protected IAccountEmailer AccountEmailer { get; } protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } + protected IOptions IdentityOptions { get; } + public AccountAppService( IdentityUserManager userManager, IIdentityRoleRepository roleRepository, IAccountEmailer accountEmailer, - IdentitySecurityLogManager identitySecurityLogManager) + IdentitySecurityLogManager identitySecurityLogManager, + IOptions identityOptions) { RoleRepository = roleRepository; AccountEmailer = accountEmailer; IdentitySecurityLogManager = identitySecurityLogManager; UserManager = userManager; + IdentityOptions = identityOptions; + LocalizationResource = typeof(AccountResource); } @@ -33,6 +39,8 @@ namespace Volo.Abp.Account { await CheckSelfRegistrationAsync(); + await IdentityOptions.SetAsync(); + var user = new IdentityUser(GuidGenerator.Create(), input.UserName, input.EmailAddress, CurrentTenant.Id); (await UserManager.CreateAsync(user, input.Password)).CheckErrors(); @@ -52,6 +60,8 @@ namespace Volo.Abp.Account public virtual async Task ResetPasswordAsync(ResetPasswordDto input) { + await IdentityOptions.SetAsync(); + var user = await UserManager.GetByIdAsync(input.UserId); (await UserManager.ResetPasswordAsync(user, input.ResetToken, input.Password)).CheckErrors(); diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index e52f6731dc..3d7381d2c3 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; using Volo.Abp.Account.Settings; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; @@ -33,17 +34,19 @@ namespace Volo.Abp.Account.Web.Pages.Account IOptions accountOptions, IIdentityServerInteractionService interaction, IClientStore clientStore, - IEventService identityServerEvents) + IEventService identityServerEvents, + IOptions identityOptions) :base( schemeProvider, - accountOptions) + accountOptions, + identityOptions) { Interaction = interaction; ClientStore = clientStore; IdentityServerEvents = identityServerEvents; } - public async override Task OnGetAsync() + public override async Task OnGetAsync() { LoginInput = new LoginInputModel(); @@ -98,7 +101,7 @@ namespace Volo.Abp.Account.Web.Pages.Account return Page(); } - public async override Task OnPostAsync(string action) + public override async Task OnPostAsync(string action) { if (action == "Cancel") { @@ -120,6 +123,8 @@ namespace Volo.Abp.Account.Web.Pages.Account ValidateModel(); + await IdentityOptions.SetAsync(); + ExternalProviders = await GetExternalProviders(); EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); @@ -173,7 +178,7 @@ namespace Volo.Abp.Account.Web.Pages.Account return RedirectSafely(ReturnUrl, ReturnUrlHash); } - public async override Task OnPostExternalLogin(string provider) + public override async Task OnPostExternalLogin(string provider) { if (AccountOptions.WindowsAuthenticationSchemeName == provider) { diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index b1dc1375cd..a499f21a6f 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -51,14 +51,17 @@ namespace Volo.Abp.Account.Web.Pages.Account protected IAuthenticationSchemeProvider SchemeProvider { get; } protected AbpAccountOptions AccountOptions { get; } + protected IOptions IdentityOptions { get; } public bool ShowCancelButton { get; set; } public LoginModel( IAuthenticationSchemeProvider schemeProvider, - IOptions accountOptions) + IOptions accountOptions, + IOptions identityOptions) { SchemeProvider = schemeProvider; + IdentityOptions = identityOptions; AccountOptions = accountOptions.Value; } @@ -91,6 +94,8 @@ namespace Volo.Abp.Account.Web.Pages.Account await ReplaceEmailToUsernameOfInputIfNeeds(); + await IdentityOptions.SetAsync(); + var result = await SignInManager.PasswordSignInAsync( LoginInput.UserNameOrEmailAddress, LoginInput.Password, diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs index acd573f916..31c9de0e18 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; using Volo.Abp.Application.Dtos; using Volo.Abp.ObjectExtending; @@ -12,16 +13,19 @@ namespace Volo.Abp.Identity { protected IdentityUserManager UserManager { get; } protected IIdentityUserRepository UserRepository { get; } - public IIdentityRoleRepository RoleRepository { get; } + protected IIdentityRoleRepository RoleRepository { get; } + protected IOptions IdentityOptions { get; } public IdentityUserAppService( IdentityUserManager userManager, IIdentityUserRepository userRepository, - IIdentityRoleRepository roleRepository) + IIdentityRoleRepository roleRepository, + IOptions identityOptions) { UserManager = userManager; UserRepository = userRepository; RoleRepository = roleRepository; + IdentityOptions = identityOptions; } //TODO: [Authorize(IdentityPermissions.Users.Default)] should go the IdentityUserAppService class. @@ -68,6 +72,8 @@ namespace Volo.Abp.Identity [Authorize(IdentityPermissions.Users.Create)] public virtual async Task CreateAsync(IdentityUserCreateDto input) { + await IdentityOptions.SetAsync(); + var user = new IdentityUser( GuidGenerator.Create(), input.UserName, @@ -88,6 +94,8 @@ namespace Volo.Abp.Identity [Authorize(IdentityPermissions.Users.Update)] public virtual async Task UpdateAsync(Guid id, IdentityUserUpdateDto input) { + await IdentityOptions.SetAsync(); + var user = await UserManager.GetByIdAsync(id); user.ConcurrencyStamp = input.ConcurrencyStamp; diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs index c08eb7ce32..9097bbc144 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; using Volo.Abp.Identity.Settings; using Volo.Abp.ObjectExtending; using Volo.Abp.Settings; @@ -13,10 +14,14 @@ namespace Volo.Abp.Identity public class ProfileAppService : IdentityAppServiceBase, IProfileAppService { protected IdentityUserManager UserManager { get; } + protected IOptions IdentityOptions { get; } - public ProfileAppService(IdentityUserManager userManager) + public ProfileAppService( + IdentityUserManager userManager, + IOptions identityOptions) { UserManager = userManager; + IdentityOptions = identityOptions; } public virtual async Task GetAsync() @@ -28,6 +33,8 @@ namespace Volo.Abp.Identity public virtual async Task UpdateAsync(UpdateProfileDto input) { + await IdentityOptions.SetAsync(); + var user = await UserManager.GetByIdAsync(CurrentUser.GetId()); if (await SettingProvider.IsTrueAsync(IdentitySettingNames.User.IsUserNameUpdateEnabled)) @@ -56,6 +63,8 @@ namespace Volo.Abp.Identity public virtual async Task ChangePasswordAsync(ChangePasswordInput input) { + await IdentityOptions.SetAsync(); + var currentUser = await UserManager.GetByIdAsync(CurrentUser.GetId()); if (currentUser.IsExternal) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index 6feddd125b..82f1b125ec 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -85,8 +85,7 @@ namespace Volo.Abp.Identity private static void AddAbpIdentityOptionsFactory(IServiceCollection services) { - services.Replace(ServiceDescriptor.Transient, AbpIdentityOptionsFactory>()); - services.Replace(ServiceDescriptor.Scoped, OptionsManager>()); + services.AddAbpDynamicOptions(); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsFactory.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsManager.cs similarity index 74% rename from modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsFactory.cs rename to modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsManager.cs index ef383ac0ad..5735f36051 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsFactory.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsManager.cs @@ -1,43 +1,25 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Volo.Abp.Identity.Settings; using Volo.Abp.Options; using Volo.Abp.Settings; -using Volo.Abp.Threading; namespace Volo.Abp.Identity { - public class AbpIdentityOptionsFactory : AbpOptionsFactory + public class AbpIdentityOptionsManager : AbpDynamicOptionsManager { protected ISettingProvider SettingProvider { get; } - public AbpIdentityOptionsFactory( - IEnumerable> setups, - IEnumerable> postConfigures, + public AbpIdentityOptionsManager(IOptionsFactory factory, ISettingProvider settingProvider) - : base(setups, postConfigures) + : base(factory) { SettingProvider = settingProvider; } - public override IdentityOptions Create(string name) - { - var options = base.Create(name); - - OverrideOptions(options); - - return options; - } - - protected virtual void OverrideOptions(IdentityOptions options) - { - AsyncHelper.RunSync(()=>OverrideOptionsAsync(options)); - } - - protected virtual async Task OverrideOptionsAsync(IdentityOptions options) + protected override async Task OverrideOptionsAsync(IdentityOptions options) { options.Password.RequiredLength = await SettingProvider.GetAsync(IdentitySettingNames.Password.RequiredLength, options.Password.RequiredLength); options.Password.RequiredUniqueChars = await SettingProvider.GetAsync(IdentitySettingNames.Password.RequiredUniqueChars, options.Password.RequiredUniqueChars); @@ -52,7 +34,6 @@ namespace Volo.Abp.Identity options.SignIn.RequireConfirmedEmail = await SettingProvider.GetAsync(IdentitySettingNames.SignIn.RequireConfirmedEmail, options.SignIn.RequireConfirmedEmail); options.SignIn.RequireConfirmedPhoneNumber = await SettingProvider.GetAsync(IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber, options.SignIn.RequireConfirmedPhoneNumber); - } } -} \ No newline at end of file +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityOptions_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityOptions_Tests.cs index e48e2171b9..0c9a682f67 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityOptions_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityOptions_Tests.cs @@ -23,28 +23,52 @@ namespace Volo.Abp.Identity } [Fact] - public void Should_Resolve_AbpIdentityOptionsFactory() + public void Should_Resolve_AbpIdentityOptionsManager() { - GetRequiredService>().ShouldBeOfType(typeof(AbpIdentityOptionsFactory)); + GetRequiredService>().ShouldBeOfType(typeof(AbpIdentityOptionsManager)); } [Fact] - public void Should_Get_Options_From_Custom_Settings_If_Available() + public async Task Should_Get_Options_From_Custom_Settings_If_Available() { using (var scope1 = ServiceProvider.CreateScope()) { - var options = scope1.ServiceProvider.GetRequiredService>().Value; - options.Password.RequiredLength.ShouldBe(6); //Default value - options.Password.RequiredUniqueChars.ShouldBe(1); //Default value + var options = scope1.ServiceProvider.GetRequiredService>(); + + //Can not get the values from the SettingProvider without options.SetAsync(); + + options.Value.Password.RequiredLength.ShouldBe(6); //Default value + options.Value.Password.RequiredUniqueChars.ShouldBe(1); //Default value + } + + using (var scope2 = ServiceProvider.CreateScope()) + { + var options = scope2.ServiceProvider.GetRequiredService>(); + var optionsValue = options.Value; + + await options.SetAsync(); + + //Still the default values because SettingProvider has not been configured yet + + optionsValue.Password.RequiredLength.ShouldBe(6); //Default value + optionsValue.Password.RequiredUniqueChars.ShouldBe(1); //Default value } - _settingProvider.GetOrNullAsync(IdentitySettingNames.Password.RequiredLength).Returns(Task.FromResult("42")); + _settingProvider + .GetOrNullAsync(IdentitySettingNames.Password.RequiredLength) + .Returns(Task.FromResult("42")); using (var scope2 = ServiceProvider.CreateScope()) { - var options = scope2.ServiceProvider.GetRequiredService>().Value; - options.Password.RequiredLength.ShouldBe(42); //Setting value - options.Password.RequiredUniqueChars.ShouldBe(1); //Default value + var options = scope2.ServiceProvider.GetRequiredService>(); + var optionsValue = options.Value; + + await options.SetAsync(); + + //Get the value from SettingProvider + + optionsValue.Password.RequiredLength.ShouldBe(42); //Setting value + optionsValue.Password.RequiredUniqueChars.ShouldBe(1); //Default value } } }