I want to add LastName field to IdentityUser table. Additionally, I want to make Surname field to appear as Middle Name just on the UI. The documentation might look enough for these, but finally, in the registration page, I want these three to appear in the order: Name, Middle Name (Surname), Last Name. More insights are welcome.
Thanks in advance!
5 Answer(s)
-
0
- Add a physical
LastNamecolumn toAbpUsers(IdentityUser)- Define the extension property in your
*.Domain.Sharedproject (YourProjectNameModuleExtensionConfigurator.cs), insideConfigureExtraProperties:
- Define the extension property in your
using System.ComponentModel.DataAnnotations; using Volo.Abp.ObjectExtending; ObjectExtensionManager.Instance.Modules().ConfigureIdentity(identity => { identity.ConfigureUser(user => { user.AddOrUpdateProperty<string>( "LastName", options => { options.Attributes.Add(new StringLengthAttribute(64)); } ); }); });- Map it to a real SQL column in your
*.EntityFrameworkCoreproject (YourProjectNameEfCoreEntityExtensionMappings.cs):
using Volo.Abp.Identity; using Volo.Abp.ObjectExtending; ObjectExtensionManager.Instance .MapEfCoreProperty<IdentityUser, string>( "LastName", (_, propertyBuilder) => { propertyBuilder.HasMaxLength(64); propertyBuilder.HasDefaultValue(""); } );- Add migration + update DB (run from the migrations/EFCore project you use):
Add-Migration Added_LastName_To_AbpUsers- Run DbMigrator /
Update-Database
Show
Surnameas “Middle Name” only on the UI- Don’t change the backend property name (
Surname) if you want to keep ABP’s defaults; just change the label/display text in the registration UI. - In MVC/Razor Pages, the “label text” is typically localized; you can override the localization value for the displayed field name (so it renders as Middle Name while still posting to
Surname).
- Don’t change the backend property name (
Make the registration page order: Name, Middle Name (Surname), Last Name
- In MVC, the reliable way is to override/customize the Register page UI and reorder the inputs in the
.cshtml. - Keep binding as:
Name→ first inputSurname→ second input (but label it “Middle Name”)ExtraProperties["LastName"](or your strongly typed extension) → third input
- In MVC, the reliable way is to override/customize the Register page UI and reorder the inputs in the
If you want
LastNameto be saved during registration- Add the input on the register form and send it as an extension property.
- In tiered solutions, ensure the Account endpoints can carry your extension property by applying entity configuration to the Account/Profile/Register DTOs (same mechanism used for profile update). The known working approach is to call
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToApi(...)in the host where the Account endpoints run (AuthServer/HttpApi.Host), including the relevant DTO types.
Less obvious references:
- https://abp.io/community/articles/rixchoha
- https://abp.io/docs/latest/framework/architecture/modularity/extending/module-entity-extensions
- https://abp.io/docs/latest/framework/architecture/modularity/extending/customizing-application-modules-extending-entities
- https://abp.io/support/questions/3517
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) - Add a physical
-
0
Hi punta,
Since your solution is tiered, I'll call out where each file goes.
1. Add a physical
LastNamecolumn onAbpUsersIn
*.Domain.Shared/YourProjectNameModuleExtensionConfigurator.cs:using System.ComponentModel.DataAnnotations; using Volo.Abp.Identity; using Volo.Abp.Localization; using Volo.Abp.ObjectExtending; ObjectExtensionManager.Instance.Modules() .ConfigureIdentity(identity => { identity.ConfigureUser(user => { user.AddOrUpdateProperty<string>( "LastName", property => { property.Attributes.Add(new StringLengthAttribute(64)); property.DisplayName = new FixedLocalizableString("Last Name"); property.UI.OnCreateForm.IsVisible = true; property.UI.OnEditForm.IsVisible = true; property.UI.OnTable.IsVisible = true; property.Configuration[IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit] = true; }); }); });In
*.EntityFrameworkCore/YourProjectNameEfCoreEntityExtensionMappings.cs:ObjectExtensionManager.Instance .MapEfCoreProperty<IdentityUser, string>( "LastName", (entityBuilder, propertyBuilder) => { propertyBuilder.HasMaxLength(64); });Then
Add-Migration Added_LastName_To_AbpUsers+ run DbMigrator.The
AllowUserToEdit = trueline is what makesLastNameautomatically render on/Account/Manage(Personal info tab) — no extra code needed for that page.2. Show
Surnameas "Middle Name" only on the UIThe
Surnamelabel is read from two different localization resources:AccountResource— used on/Account/Manage(Pro Account module)IdentityResource— used on/Identity/Usersadmin pages
Override both for the change to be consistent. Don't translate or rename the property in code, just override the localization keys.
Create
Localization/AccountOverrides/en.jsonandLocalization/IdentityOverrides/en.jsonin your*.AuthServerproject, both with:{ "Culture": "en", "Texts": { "DisplayName:Surname": "Middle Name" } }Mark them as
EmbeddedResourcein the.csproj, then inYourProjectNameAuthServerModule.ConfigureServices:Configure<AbpLocalizationOptions>(options => { options.Resources .Get<Volo.Abp.Account.Localization.AccountResource>() .AddVirtualJson("/Localization/AccountOverrides"); options.Resources .Get<Volo.Abp.Identity.Localization.IdentityResource>() .AddVirtualJson("/Localization/IdentityOverrides"); });For other languages just add
zh-Hans.json,de.json, etc. in the same folders.3. Reorder Register page fields: Name → Middle Name (Surname) → Last Name
The
RegisterModelyou'll be subclassing isVolo.Abp.Account.Public.Web.Pages.Account.RegisterModelfrom the Pro Account module (NuGet packageVolo.Abp.Account.Pro.Public.Web). Its defaultPostInputonly containsUserName/EmailAddress/Password/Code— Name and Surname aren't collected during registration out of the box, so you need to extend it.In
*.AuthServer/Pages/Account/Register.cshtml.cs:using System.ComponentModel.DataAnnotations; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Volo.Abp.Account.ExternalProviders; using Volo.Abp.Account.Public.Web; using Volo.Abp.Account.Public.Web.Pages.Account; using Volo.Abp.Data; using Volo.Abp.Identity; using Volo.Abp.Security.Claims; using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace YourCompany.YourProject.AuthServer.Pages.Account; public class CustomRegisterModel : RegisterModel { [BindProperty] public new CustomPostInput Input { get; set; } = new(); public CustomRegisterModel( IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IAccountExternalProviderAppService accountExternalProviderAppService, ICurrentPrincipalAccessor currentPrincipalAccessor, IHttpClientFactory httpClientFactory) : base(schemeProvider, accountOptions, accountExternalProviderAppService, currentPrincipalAccessor, httpClientFactory) { } public override Task<IActionResult> OnPostAsync() { // Sync the extended input back to the base Input so base.OnPostAsync sees UserName/Email/Password/Code. base.Input = new PostInput { UserName = Input.UserName, EmailAddress = Input.EmailAddress, Password = Input.Password, Code = Input.Code }; return base.OnPostAsync(); } protected override async Task<IdentityUser> RegisterLocalUserAsync() { // Base call creates the user via AccountAppService.RegisterAsync (UserName/Email/Password only). var user = await base.RegisterLocalUserAsync(); user.Name = Input.Name; user.Surname = Input.Surname; user.SetProperty("LastName", Input.LastName); (await UserManager.UpdateAsync(user)).CheckErrors(); return user; } public class CustomPostInput : PostInput { [Required] [StringLength(64)] public string Name { get; set; } [StringLength(64)] public string Surname { get; set; } [StringLength(64)] public string LastName { get; set; } } }Then
*.AuthServer/Pages/Account/Register.cshtml(copy the original fromVolo.Abp.Account.Pro.Public.Web/Pages/Account/Register.cshtmland adjust). The important parts:@model YourCompany.YourProject.AuthServer.Pages.Account.CustomRegisterModel ... @* keep the default UserName / Email / Password blocks here *@ @* === Custom fields in the order you want === *@ <div class="form-floating mb-2"> <input asp-for="Input.Name" type="text" class="form-control" placeholder="Name"> <label asp-for="Input.Name">Name</label> <span asp-validation-for="Input.Name" /> </div> <div class="form-floating mb-2"> <input asp-for="Input.Surname" type="text" class="form-control" placeholder="Middle Name"> <label asp-for="Input.Surname">Middle Name</label> <span asp-validation-for="Input.Surname" /> </div> <div class="form-floating mb-3"> <input asp-for="Input.LastName" type="text" class="form-control" placeholder="Last Name"> <label asp-for="Input.LastName">Last Name</label> <span asp-validation-for="Input.LastName" /> </div>ABP's Razor Pages override is path-based: a same-path
.cshtmlin the AuthServer project takes precedence over the one shipped inVolo.Abp.Account.Pro.Public.Web.Quick recap on file locations (tiered)
| File | Project | |---|---| |
ModuleExtensionConfigurator(LastName property) |*.Domain.Shared| |EfCoreEntityExtensionMappings+ Migration |*.EntityFrameworkCore| |Register.cshtml+Register.cshtml.cs|*.AuthServer| |AccountOverrides/*.json+IdentityOverrides/*.json+Configure<AbpLocalizationOptions>|*.AuthServer|Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi punta,
Since your solution is tiered, I'll call out where each file goes.
1. Add a physical
LastNamecolumn onAbpUsersIn
*.Domain.Shared/YourProjectNameModuleExtensionConfigurator.cs:using System.ComponentModel.DataAnnotations; using Volo.Abp.Identity; using Volo.Abp.Localization; using Volo.Abp.ObjectExtending; ObjectExtensionManager.Instance.Modules() .ConfigureIdentity(identity => { identity.ConfigureUser(user => { user.AddOrUpdateProperty<string>( "LastName", property => { property.Attributes.Add(new StringLengthAttribute(64)); property.DisplayName = new FixedLocalizableString("Last Name"); property.UI.OnCreateForm.IsVisible = true; property.UI.OnEditForm.IsVisible = true; property.UI.OnTable.IsVisible = true; property.Configuration[IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit] = true; }); }); });In
*.EntityFrameworkCore/YourProjectNameEfCoreEntityExtensionMappings.cs:ObjectExtensionManager.Instance .MapEfCoreProperty<IdentityUser, string>( "LastName", (entityBuilder, propertyBuilder) => { propertyBuilder.HasMaxLength(64); });Then
Add-Migration Added_LastName_To_AbpUsers+ run DbMigrator.The
AllowUserToEdit = trueline is what makesLastNameautomatically render on/Account/Manage(Personal info tab) — no extra code needed for that page.2. Show
Surnameas "Middle Name" only on the UIThe
Surnamelabel is read from two different localization resources:AccountResource— used on/Account/Manage(Pro Account module)IdentityResource— used on/Identity/Usersadmin pages
Override both for the change to be consistent. Don't translate or rename the property in code, just override the localization keys.
Create
Localization/AccountOverrides/en.jsonandLocalization/IdentityOverrides/en.jsonin your*.AuthServerproject, both with:{ "Culture": "en", "Texts": { "DisplayName:Surname": "Middle Name" } }Mark them as
EmbeddedResourcein the.csproj, then inYourProjectNameAuthServerModule.ConfigureServices:Configure<AbpLocalizationOptions>(options => { options.Resources .Get<Volo.Abp.Account.Localization.AccountResource>() .AddVirtualJson("/Localization/AccountOverrides"); options.Resources .Get<Volo.Abp.Identity.Localization.IdentityResource>() .AddVirtualJson("/Localization/IdentityOverrides"); });For other languages just add
zh-Hans.json,de.json, etc. in the same folders.3. Reorder Register page fields: Name → Middle Name (Surname) → Last Name
The
RegisterModelyou'll be subclassing isVolo.Abp.Account.Public.Web.Pages.Account.RegisterModelfrom the Pro Account module (NuGet packageVolo.Abp.Account.Pro.Public.Web). Its defaultPostInputonly containsUserName/EmailAddress/Password/Code— Name and Surname aren't collected during registration out of the box, so you need to extend it.In
*.AuthServer/Pages/Account/Register.cshtml.cs:using System.ComponentModel.DataAnnotations; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Volo.Abp.Account.ExternalProviders; using Volo.Abp.Account.Public.Web; using Volo.Abp.Account.Public.Web.Pages.Account; using Volo.Abp.Data; using Volo.Abp.Identity; using Volo.Abp.Security.Claims; using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace YourCompany.YourProject.AuthServer.Pages.Account; public class CustomRegisterModel : RegisterModel { [BindProperty] public new CustomPostInput Input { get; set; } = new(); public CustomRegisterModel( IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IAccountExternalProviderAppService accountExternalProviderAppService, ICurrentPrincipalAccessor currentPrincipalAccessor, IHttpClientFactory httpClientFactory) : base(schemeProvider, accountOptions, accountExternalProviderAppService, currentPrincipalAccessor, httpClientFactory) { } public override Task<IActionResult> OnPostAsync() { // Sync the extended input back to the base Input so base.OnPostAsync sees UserName/Email/Password/Code. base.Input = new PostInput { UserName = Input.UserName, EmailAddress = Input.EmailAddress, Password = Input.Password, Code = Input.Code }; return base.OnPostAsync(); } protected override async Task<IdentityUser> RegisterLocalUserAsync() { // Base call creates the user via AccountAppService.RegisterAsync (UserName/Email/Password only). var user = await base.RegisterLocalUserAsync(); user.Name = Input.Name; user.Surname = Input.Surname; user.SetProperty("LastName", Input.LastName); (await UserManager.UpdateAsync(user)).CheckErrors(); return user; } public class CustomPostInput : PostInput { [Required] [StringLength(64)] public string Name { get; set; } [StringLength(64)] public string Surname { get; set; } [StringLength(64)] public string LastName { get; set; } } }Then
*.AuthServer/Pages/Account/Register.cshtml(copy the original fromVolo.Abp.Account.Pro.Public.Web/Pages/Account/Register.cshtmland adjust). The important parts:@model YourCompany.YourProject.AuthServer.Pages.Account.CustomRegisterModel ... @* keep the default UserName / Email / Password blocks here *@ @* === Custom fields in the order you want === *@ <div class="form-floating mb-2"> <input asp-for="Input.Name" type="text" class="form-control" placeholder="Name"> <label asp-for="Input.Name">Name</label> <span asp-validation-for="Input.Name" /> </div> <div class="form-floating mb-2"> <input asp-for="Input.Surname" type="text" class="form-control" placeholder="Middle Name"> <label asp-for="Input.Surname">Middle Name</label> <span asp-validation-for="Input.Surname" /> </div> <div class="form-floating mb-3"> <input asp-for="Input.LastName" type="text" class="form-control" placeholder="Last Name"> <label asp-for="Input.LastName">Last Name</label> <span asp-validation-for="Input.LastName" /> </div>ABP's Razor Pages override is path-based: a same-path
.cshtmlin the AuthServer project takes precedence over the one shipped inVolo.Abp.Account.Pro.Public.Web.Quick recap on file locations (tiered)
| File | Project | |---|---| |
ModuleExtensionConfigurator(LastName property) |*.Domain.Shared| |EfCoreEntityExtensionMappings+ Migration |*.EntityFrameworkCore| |Register.cshtml+Register.cshtml.cs|*.AuthServer| |AccountOverrides/*.json+IdentityOverrides/*.json+Configure<AbpLocalizationOptions>|*.AuthServer|Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)