- ABP Framework version: v5.2.1
- UI type: MVC
- DB provider: EF Core
- Tiered (MVC) or Identity Server Separated (Angular): yes
- Exception message and stack trace: This is a follow-up to my previous issue here, which was closed but not fully resolved: [https://support.abp.io/QA/Questions/2792/How-to-add-to-extend-the-Identity-Server-Personal-info-page#answer-c4fad5d5-e30a-85db-b30b-3a02e5558be9]
To reiterate, I am adding an extra property on the User Personal Info tab for Communication Language, that can be set by the user. I have the property displaying on the view component but updating the value throws an error:
Your request is not valid! The following errors were detected during validation. - The field Language is invalid.
- Steps to reproduce the issue:"
To replace the personal info tab, I did the following in the Identity Server project:
- Created a new class MyAccountProfileManagementPageContributor that implements the IProfileManagementPageContributor interface and removes the framework supplied PersonalInfo tab and adds a custom version in place:
public class MyAccountProfileManagementPageContributor : IProfileManagementPageContributor
{
public async Task ConfigureAsync(ProfileManagementPageCreationContext context)
{
var l = context.ServiceProvider.GetRequiredService<IStringLocalizer<AccountResource>>();
context.Groups.Remove(context.Groups.First(x => x.Id == "Volo-Abp-Account-PersonalInfo"));
context.Groups.Add(
new ProfileManagementPageGroup(
"Volo-Abp-Account-PersonalInfo",
l["ProfileTab:PersonalInfo"],
typeof(MyAccountProfilePersonalInfoManagementGroupViewComponent)
)
);
}
}
- Configured the module to add the MyAccountProfileManagementPageContributor, in the ConfigureServices method:
Configure<ProfileManagementPageOptions>(options =>
{
options.Contributors.Add(new MyAccountProfileManagementPageContributor());
});
- Created a PortalIdentityDtoExtensions class and added the extra property to the relevant Dtos:
public static void Configure()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.AddOrUpdateProperty<ProfileDto, Language>(AccountExtensionProperties.Language)
.AddOrUpdateProperty<UpdateProfileDto, Language>(AccountExtensionProperties.Language)
.AddOrUpdateProperty<MyPersonalInfoModel, Language>(AccountExtensionProperties.Language);
});
}
- Configured the PortalIdentityDtoExtensions in the module:
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PortalIdentityDtoExtensions.Configure();
}
- Created a version of the AccountProfilePersonalInfoManagementGroupViewComponent:
namespace Volo.Abp.Account.Public.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo;
public class MyAccountProfilePersonalInfoManagementGroupViewComponent : AccountProfilePersonalInfoManagementGroupViewComponent
{
public MyAccountProfilePersonalInfoManagementGroupViewComponent(
IProfileAppService profileAppService): base(profileAppService)
{
}
public override async Task<IViewComponentResult> InvokeAsync()
{
var user = await ProfileAppService.GetAsync();
var model = ObjectMapper.Map<ProfileDto, MyPersonalInfoModel>(user);
return View("~/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml", model);
}
public class MyPersonalInfoModel : ExtensibleObject
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))]
[Display(Name = "DisplayName:UserName")]
public string UserName { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
[Display(Name = "DisplayName:Email")]
public string Email { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxNameLength))]
[Display(Name = "DisplayName:Name")]
public string Name { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxSurnameLength))]
[Display(Name = "DisplayName:Surname")]
public string Surname { get; set; }
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPhoneNumberLength))]
[Display(Name = "DisplayName:PhoneNumber")]
public string PhoneNumber { get; set; }
public bool PhoneNumberConfirmed { get; set; }
public bool EmailConfirmed { get; set; }
}
}
Added a Default.cshtml to reference MyPersonalInfoModel and added handling for extra properties: (see \Volo.Abp.Account.Pro.Public.Web\Pages\Account\Components\ProfileManagementGroup\PersonalInfo\Default.cshtml for rest of page)
Added the auto mapper profile:
public class PortalIdentityServerAutoMapperProfile : Profile
{
public PortalIdentityServerAutoMapperProfile()
{
CreateMap<ProfileDto, MyAccountProfilePersonalInfoManagementGroupViewComponent.MyPersonalInfoModel>().MapExtraProperties();
CreateMap<IdentitySecurityLog, IdentitySecurityLogDto>(); // can remove 5.2-RC2 https://github.com/abpframework/abp/issues/12070
}
}
- Configured the auto mapper profile in the module:
context.Services.AddAutoMapperObjectMapper<PortalIdentityServerModule>();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<PortalIdentityServerAutoMapperProfile>(validate: true);
});
Appreciate your assistance.
4 Answer(s)
-
0
hi
Can you direct share a template project with me? liming.ma@volosoft.com
I will check it locally.
-
0
hi
Can you direct share a template project with me? liming.ma@volosoft.com
I will check it locally.
sent shared link to your email. thx
-
0
hi
Configure<Microsoft.AspNetCore.Mvc.JsonOptions>(options => { options.JsonSerializerOptions.Converters.Add(new UpdateProfileDtoConverter()); }); using System; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using Volo.Abp.Account; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; namespace Acs.Cts.Portal; public class UpdateProfileDtoConverter : JsonConverter<UpdateProfileDto>, ITransientDependency { private JsonSerializerOptions _readJsonSerializerOptions; private JsonSerializerOptions _writeJsonSerializerOptions; public override UpdateProfileDto Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { _readJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this); var rootElement = JsonDocument.ParseValue(ref reader).RootElement; var obj = rootElement.Deserialize<UpdateProfileDto>(_readJsonSerializerOptions); var extraProperties = rootElement.EnumerateObject().Where(x => x.Name.StartsWith("extraProperties[", StringComparison.OrdinalIgnoreCase)).ToList(); foreach (var extraProperty in extraProperties) { var x = extraProperty.Name.IndexOf("[", StringComparison.InvariantCultureIgnoreCase); var y = extraProperty.Name.Length - 2 - x; if (x <= 0 || y <= 0) { continue; } var key = extraProperty.Name.Substring(x + 1, y); var value = extraProperty.Value.GetString(); obj.SetProperty(key, value); } return obj; } public override void Write(Utf8JsonWriter writer, UpdateProfileDto value, JsonSerializerOptions options) { _writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this); JsonSerializer.Serialize(writer, value, value.GetType(), _writeJsonSerializerOptions); } }
-
0
This works - thanks!