Open Closed

Overriding PersonalInfo for extra properties #2989


User avatar
1
AlderCove created
  • 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:

  1. 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)
            )
        );
    }
}
  1. Configured the module to add the MyAccountProfileManagementPageContributor, in the ConfigureServices method:
        Configure<ProfileManagementPageOptions>(options =>
        {
            options.Contributors.Add(new MyAccountProfileManagementPageContributor());
        });
  1. 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);
        });
    }
  1. Configured the PortalIdentityDtoExtensions in the module:
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        PortalIdentityDtoExtensions.Configure();
    }
  1. 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; }
    }
}    
  1. 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)

  2. 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
    }
}
  1. Configured the auto mapper profile in the module:
        context.Services.AddAutoMapperObjectMapper<PortalIdentityServerModule>();
        Configure<AbpAutoMapperOptions>(options =>
        {
            options.AddProfile<PortalIdentityServerAutoMapperProfile>(validate: true);
        });

Appreciate your assistance.

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

4 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you direct share a template project with me? liming.ma@volosoft.com

    I will check it locally.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    AlderCove created

    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

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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);
        }
    }
    
    
    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    AlderCove created

    This works - thanks!

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 16, 2026, 14:50
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.