diff --git a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs index a6e927f6e0..07a3345e33 100644 --- a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs +++ b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs @@ -46,6 +46,14 @@ namespace Volo.Abp.AutoMapper } } + void ValidateAll(IConfigurationProvider config) + { + foreach (var profileType in options.ValidatingProfiles) + { + config.AssertConfigurationIsValid(((Profile)Activator.CreateInstance(profileType)).ProfileName); + } + } + if (options.UseStaticMapper) { //We should prevent duplicate mapping in an application, since Mapper is static. @@ -56,6 +64,8 @@ namespace Volo.Abp.AutoMapper ConfigureAll(new AbpAutoMapperConfigurationContext(mapperConfigurationExpression, scope.ServiceProvider)); }); + ValidateAll(Mapper.Configuration); + _createdMappingsBefore = true; } @@ -68,6 +78,8 @@ namespace Volo.Abp.AutoMapper ConfigureAll(new AbpAutoMapperConfigurationContext(mapperConfigurationExpression, scope.ServiceProvider)); }); + ValidateAll(config); + scope.ServiceProvider.GetRequiredService().Mapper = config.CreateMapper(); } } diff --git a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperOptions.cs b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperOptions.cs index 030084764b..65a5cf211f 100644 --- a/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperOptions.cs +++ b/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperOptions.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using AutoMapper; +using Volo.Abp.Collections; namespace Volo.Abp.AutoMapper { @@ -7,12 +9,29 @@ namespace Volo.Abp.AutoMapper { public List> Configurators { get; } + public ITypeList ValidatingProfiles { get; set; } + public bool UseStaticMapper { get; set; } public AbpAutoMapperOptions() { UseStaticMapper = true; Configurators = new List>(); + ValidatingProfiles = new TypeList(); + } + + public void AddProfile(bool validate = false) + where TProfile: Profile, new() + { + Configurators.Add(context => + { + context.MapperConfiguration.AddProfile(); + }); + + if (validate) + { + ValidatingProfiles.Add(); + } } } } \ No newline at end of file diff --git a/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs b/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs index b700fe9d64..8877a8077e 100644 --- a/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs +++ b/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs @@ -17,9 +17,9 @@ namespace Volo.Abp.Identity [MaxLength(IdentityUserConsts.MaxPhoneNumberLength)] public string PhoneNumber { get; set; } - public bool TwoFactorEnabled { get; set; } //TODO: Optional? + public bool TwoFactorEnabled { get; set; } - public bool LockoutEnabled { get; set; } //TODO: Optional? + public bool LockoutEnabled { get; set; } [CanBeNull] public string[] RoleNames { get; set; } diff --git a/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs b/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs index 47ce01f194..d2721a1911 100644 --- a/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs +++ b/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs @@ -17,8 +17,8 @@ namespace Volo.Abp.Identity public bool TwoFactorEnabled { get; set; } - public DateTimeOffset? LockoutEnd { get; set; } - public bool LockoutEnabled { get; set; } + + public DateTimeOffset? LockoutEnd { get; set; } } } \ No newline at end of file diff --git a/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs b/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs index e63983ea9c..a9a74902b9 100644 --- a/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs +++ b/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs @@ -1,6 +1,4 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.AutoMapper; @@ -47,10 +45,7 @@ namespace Volo.Abp.Identity.Web services.Configure(options => { - options.Configurators.Add(context => - { - context.MapperConfiguration.AddProfile(); - }); + options.AddProfile(true); }); } } diff --git a/src/Volo.Abp.Identity.Web/Areas/Identity/Localization/Resource/en.json b/src/Volo.Abp.Identity.Web/Areas/Identity/Localization/Resource/en.json index ec605b4f53..2bb8a69329 100644 --- a/src/Volo.Abp.Identity.Web/Areas/Identity/Localization/Resource/en.json +++ b/src/Volo.Abp.Identity.Web/Areas/Identity/Localization/Resource/en.json @@ -14,6 +14,8 @@ "Password": "Password", "UserDeletionConfirmationMessage": "User '{0}' will be deleted. Do you confirm that?", "MaxLengthKey": "The field {0} must be a string or array type with a maximum length of '{1}'.", - "The {0} field is required.": "The {0} field is required." + "The {0} field is required.": "The {0} field is required.", + "TwoFactorVerification": "Two factor verification", + "AccountLockoutOnFailedLoginAttempts": "Locking account after failed login attempts" } } \ No newline at end of file diff --git a/src/Volo.Abp.Identity.Web/Areas/Identity/Localization/Resource/tr.json b/src/Volo.Abp.Identity.Web/Areas/Identity/Localization/Resource/tr.json index 02564939cf..3eafc7d2c5 100644 --- a/src/Volo.Abp.Identity.Web/Areas/Identity/Localization/Resource/tr.json +++ b/src/Volo.Abp.Identity.Web/Areas/Identity/Localization/Resource/tr.json @@ -14,6 +14,8 @@ "Password": "Şifre", "UserDeletionConfirmationMessage": "{0} kullanıcısı silinecektir. Onaylıyor musunuz?", "The field {0} must be a string or array type with a maximum length of '{1}'.": "{0} alanı en fazla '{1}' karakter uzunluğunda olmalıdır.", - "The {0} field is required.": "{0} alanı zorunludur." + "The {0} field is required.": "{0} alanı zorunludur.", + "TwoFactorVerification": "İki aşamalı doğrumala", + "AccountLockoutOnFailedLoginAttempts": "Başarısız giriş denemeleri sonrası hesabı kilitleme" } } \ No newline at end of file diff --git a/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml b/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml index 2642ddda2c..99e94c4e7b 100644 --- a/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml +++ b/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml @@ -48,6 +48,18 @@ +
+ +
+
+ +
diff --git a/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs b/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs index 2fb0a4a25a..0e58eedbe1 100644 --- a/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs +++ b/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs @@ -67,6 +67,12 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users [MaxLength(IdentityUserConsts.MaxPhoneNumberLength)] [Display(Name = "PhoneNumber")] public string PhoneNumber { get; set; } + + [Display(Name = "TwoFactorVerification")] + public bool TwoFactorEnabled { get; set; } = true; + + [Display(Name = "AccountLockoutOnFailedLoginAttempts")] + public bool LockoutEnabled { get; set; } = true; } public class AssignedRoleViewModel diff --git a/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml b/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml index 2e6605dfb7..aeb3c958fa 100644 --- a/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml +++ b/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml @@ -45,6 +45,18 @@
+
+ +
+
+ +
diff --git a/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs b/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs index ee689ad32e..0d18e14716 100644 --- a/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs +++ b/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs @@ -73,6 +73,12 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users [MaxLength(IdentityUserConsts.MaxPhoneNumberLength)] [Display(Name = "PhoneNumber")] public string PhoneNumber { get; set; } + + [Display(Name = "TwoFactorVerification")] + public bool TwoFactorEnabled { get; set; } + + [Display(Name = "AccountLockoutOnFailedLoginAttempts")] + public bool LockoutEnabled { get; set; } } public class AssignedRoleViewModel diff --git a/src/Volo.Abp.TestBase/AbpIntegratedTest.cs b/src/Volo.Abp.TestBase/AbpIntegratedTest.cs index 7832e384b5..c794df8258 100644 --- a/src/Volo.Abp.TestBase/AbpIntegratedTest.cs +++ b/src/Volo.Abp.TestBase/AbpIntegratedTest.cs @@ -37,7 +37,7 @@ namespace Volo.Abp.TestBase protected virtual void BeforeAddApplication(IServiceCollection services) { - + } protected virtual void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) diff --git a/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_ConfigurationValidation_Tests.cs b/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_ConfigurationValidation_Tests.cs new file mode 100644 index 0000000000..65352ea9e7 --- /dev/null +++ b/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_ConfigurationValidation_Tests.cs @@ -0,0 +1,73 @@ +using AutoMapper; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.TestBase; +using Xunit; +using IObjectMapper = Volo.Abp.ObjectMapping.IObjectMapper; + +namespace Volo.Abp.AutoMapper +{ + public class AutoMapper_ConfigurationValidation_Tests : AbpIntegratedTest + { + private readonly IObjectMapper _objectMapper; + + public AutoMapper_ConfigurationValidation_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Should_Validate_Configuration() + { + _objectMapper.Map(new MySourceClass {Value = "42"}).Value.ShouldBe("42"); + _objectMapper.Map(new MySourceClass {Value = "42"}).ValueNotMatched.ShouldBe(null); + } + + [DependsOn(typeof(AbpAutoMapperModule))] + public class Module : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + options.UseStaticMapper = false; + + options.AddProfile(true); + options.AddProfile(); + }); + } + } + + public class ValidatedProfile : Profile + { + public ValidatedProfile() + { + CreateMap(); + } + } + + public class NonValidatedProfile : Profile + { + public NonValidatedProfile() + { + CreateMap(); + } + } + + public class MySourceClass + { + public string Value { get; set; } + } + + public class MyClassValidated + { + public string Value { get; set; } + } + + public class MyClassNonValidated + { + public string ValueNotMatched { get; set; } + } + } +} diff --git a/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutofacTestModule.cs b/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutofacTestModule.cs index 46d5e49e55..4dda1020e9 100644 --- a/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutofacTestModule.cs +++ b/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutofacTestModule.cs @@ -8,6 +8,11 @@ namespace Volo.Abp.AutoMapper { public override void ConfigureServices(IServiceCollection services) { + services.Configure(options => + { + options.UseStaticMapper = false; + }); + services.AddAssemblyOf(); } }