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.


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.

  • 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

  • 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);
        }
    }
    
    
  • User Avatar
    0
    AlderCove created

    This works - thanks!

Made with ❤️ on ABP v9.1.0-preview. Updated on December 05, 2024, 12:19