# Module Entity Extensions Module entity extension system is a **high level** extension system that allows you to **define new properties** for existing entities of the depended modules. It automatically **adds properties to the entity, database, HTTP API and the user interface** in a single point. > The module must be developed the *Module Entity Extensions* system in mind. All the **official modules** supports this system wherever possible. ## Quick Example Open the *YourProjectNameModuleExtensionConfigurator* class inside the `Domain.Shared` project of your solution and change the `ConfigureExtraProperties`method as shown below to add a `SocialSecurityNumber` property to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). ````csharp public static void ConfigureExtraProperties() { OneTimeRunner.Run(() => { ObjectExtensionManager.Instance.Modules() .ConfigureIdentity(identity => { identity.ConfigureUser(user => { user.AddOrUpdateProperty( //property type: string "SocialSecurityNumber", //property name property => { //validation rules property.Attributes.Add(new RequiredAttribute()); property.Attributes.Add( new StringLengthAttribute(64) { MinimumLength = 4 } ); //...other configurations for this property } ); }); }); }); } ```` >This method is called inside the `YourProjectNameDomainSharedModule` at the beginning of the application. `OneTimeRunner` is a utility class that guarantees to execute this code only one time per application, since multiple calls are unnecessary. * `ObjectExtensionManager.Instance.Modules()` is the starting point to configure a module. `ConfigureIdentity(...)` method is used to configure the entities of the Identity Module. * `identity.ConfigureUser(...)` is used to configure the user entity of the identity module. Not all entities are designed to be extensible (since it is not needed). Use the intellisense to discover the extensible modules and entities. * `user.AddOrUpdateProperty(...)` is used to add a new property to the user entity with the `string` type (`AddOrUpdateProperty` method can be called multiple times for the same property of the same entity. Each call can configure the options of the same property, but only one property is added to the entity with the same property name). You can call this method with different property names to add more properties. * `SocialSecurityNumber` is the name of the new property. * `AddOrUpdateProperty` gets a second argument (the `property =>` lambda expression) to configure additional options for the new property. * We can add data annotation attributes like shown here, just like adding a data annotation attribute to a class property. #### Create & Update Forms Once you define a property, it appears in the create and update forms of the related entity: ![add-new-property-to-user-form](images/add-new-property-to-user-form.png) `SocialSecurityNumber` field comes into the form. Next sections will explain the localization and the validation for this new property. ### Data Table New properties also appear in the data table of the related page: ![add-new-property-to-user-form](images/add-new-property-to-user-table.png) `SocialSecurityNumber` column comes into the table. Next sections will explain the option to hide this column from the data table. ## Property Options There are some options that you can configure while defining a new property. ### Display Name You probably want to set a different (human readable) display name for the property that is shown on the user interface. #### Don't Want to Localize? If your application is not localized, you can directly set the `DisplayName` for the property to a `FixedLocalizableString` object. Example: ````csharp property => { property.DisplayName = new FixedLocalizableString("Social security no"); } ```` #### Localizing the Display Name If you want to localize the display name, you have two options. ##### Localize by Convention Instead of setting the `property.DisplayName`, you can directly open your localization file (like `en.json`) and add the following entry to the `texts` section: ````json "SocialSecurityNumber": "Social security no" ```` Define the same `SocialSecurityNumber` key (the property name you've defined before) in your localization file for each language you support. That's all! In some cases, the localization key may conflict with other keys in your localization files. In such cases, you can use the `DisplayName:` prefix for display names in the localization file (`DisplayName:SocialSecurityNumber` as the localization key for this example). Extension system looks for prefixed version first, then fallbacks to the non prefixed name (it then fallbacks to the property name if you haven't localized it). > This approach is recommended since it is simple and suitable for most scenarios. ##### Localize using the `DisplayName` Property If you want to specify the localization key or the localization resource, you can still set the `DisplayName` option: ````csharp property => { property.DisplayName = LocalizableString.Create( "UserSocialSecurityNumberDisplayName" ); } ```` * `MyProjectNameResource` is the localization resource and `UserSocialSecurityNumberDisplayName` is the localization key in the localization resource. > See [the localization document](Localization.md) if you want to learn more about the localization system. #### Default Value A default value is automatically set for the new property, which is the natural default value for the property type, like `null` for `string`, `false` for `bool` or `0` for `int`. There are two ways to override the default value: ##### DefaultValue Option `DefaultValue` option can be set to any value: ````csharp property => { property.DefaultValue = 42; } ```` ##### DefaultValueFactory Options `DefaultValueFactory` can be set to a function that returns the default value: ````csharp property => { property.DefaultValueFactory = () => DateTime.Now; } ```` `options.DefaultValueFactory` has a higher priority than the `options.DefaultValue` . > Tip: Use `DefaultValueFactory` option only if the default value may change over the time (like `DateTime.Now` in this example). If it is a constant value, then use the `DefaultValue` option. ### Validation Entity extension system allows you to define validation for extension properties in a few ways. #### Data Annotation Attributes `Attributes` is a list of attributes associated to this property. The example code below adds two [data annotation validation attributes](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation) to the property: ````csharp property => { property.Attributes.Add(new RequiredAttribute()); property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4}); } ```` When you run the application, you see that the validation works out of the box: ![add-new-propert-to-user-form](images/add-new-property-to-user-form-validation-error.png) Since we've added the `RequiredAttribute`, it doesn't allow to left it blank. The validation system works; * On the user interface (with automatic localization). * On the HTTP API. Even if you directly perform an HTTP request, you get validation errors with a proper HTTP status code. * On the `SetProperty(...)` method on the entity (see [the document](Entities.md) if you wonder what is the `SetProperty()` method). So, it automatically makes a full stack validation. > See the [ASP.NET Core MVC Validation document](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation) to learn more about the attribute based validation. ##### Default Validation Attributes There are some attributes **automatically added** when you create certain type of properties; * `RequiredAttribute` is added for **non nullable** primitive property types (e.g. `int`, `bool`, `DateTime`...) and `enum` types. If you want to allow nulls, make the property nullable (e.g. `int?`). * `EnumDataTypeAttribute` is added for **enum types**, to prevent to set invalid enum values. Use `property.Attributes.Clear();` if you don't want these attributes. #### Validation Actions Validation actions allows you to execute a custom code to perform the validation. The example below checks if the `SocialSecurityNumber` starts with `B` and adds a validation error if so: ````csharp property => { property.Attributes.Add(new RequiredAttribute()); property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4}); property.Validators.Add(context => { if (((string) context.Value).StartsWith("B")) { context.ValidationErrors.Add( new ValidationResult( "Social security number can not start with the letter 'B', sorry!", new[] {"extraProperties.SocialSecurityNumber"} ) ); } }); } ```` Using a `RegularExpressionAttribute` might be better in this case, but this is just an example. Anyway, if you enter a value starts with the letter `B` you get the following error **while saving the form**: ![add-new-propert-to-user-form](images/add-new-property-to-user-form-validation-error-custom.png) ##### The Context Object The `context` object has useful properties that can be used in your custom validation action. For example, you can use the `context.ServiceProvider` to resolve services from the [dependency injection system](Dependency-Injection.md). The example below gets the localizer and adds a localized error message: ````csharp if (((string) context.Value).StartsWith("B")) { var localizer = context.ServiceProvider .GetRequiredService>(); context.ValidationErrors.Add( new ValidationResult( localizer["SocialSecurityNumberCanNotStartWithB"], new[] {"extraProperties.SocialSecurityNumber"} ) ); } ```` >`context.ServiceProvider` is nullable! It can be `null` only if you use the `SetProperty(...)` method on the object. Because DI system is not available on this time. While this is a rare case, you should perform a fallback logic when `context.ServiceProvider` is `null`. For this example, you would add a non-localized error message. This is not a problem since setting an invalid value to a property generally is a programmer mistake and you mostly don't need to localization in this case. In any way, you would not be able to use localization even in a regular property setter. But, if you are serious about localization, you can throw a business exception (see the [exception handling document](https://docs.abp.io/en/abp/latest/Exception-Handling) to learn how to localize a business exception). ### UI Visibility When you define a property, it appears on the data table, create and edit forms on the related UI page. However, you can control each one individually. Example: ````csharp property => { property.UI.OnTable.IsVisible = false; //...other configurations } ```` Use `property.UI.OnCreateForm` and `property.UI.OnEditForm` to control forms too. If a property is required, but not added to the create form, you definitely get a validation exception, so use this option carefully. But a required property may not be in the edit form if that's your requirement. ### HTTP API Availability Even if you disable a property on UI, it can be still available through the HTTP API. By default, a property is available on all APIs. Use the `property.Api` options to make a property unavailable in some API endpoints. ````csharp property => { property.Api.OnUpdate.IsAvailable = false; } ```` In this example, Update HTTP API will not allow to set a new value to this property. In this case, you also want to disable this property on the edit form: ````csharp property => { property.Api.OnUpdate.IsAvailable = false; property.UI.OnEditForm.IsVisible = false; } ```` In addition to the `property.Api.OnUpdate`, you can set `property.Api.OnCreate` and `property.Api.OnGet` for a fine control the API endpoint. ## Special Types ### Enum Module extension system naturally supports the `enum` types. An example enum type: ````csharp public enum UserType { Regular, Moderator, SuperUser } ```` You can add enum properties just like others: ````csharp user.AddOrUpdateProperty("Type"); ```` An enum properties is shown as combobox (select) in the create/edit forms: ![add-new-property-enum](images/add-new-property-enum.png) #### Localization Enum member name is shown on the table and forms by default. If you want to localize it, just create a new entry on your [localization](https://docs.abp.io/en/abp/latest/Localization) file: ````json "UserType.SuperUser": "Super user" ```` One of the following names can be used as the localization key: * `Enum:UserType.SuperUser` * `UserType.SuperUser` * `SuperUser` Localization system searches for the key with the given order. Localized text are used on the table and the create/edit forms. ### Navigation Properties / Foreign Keys It is supported to add an extension property to an entity that is Id of another entity (foreign key). #### Example: Associate a department to a user ````csharp ObjectExtensionManager.Instance.Modules() .ConfigureIdentity(identity => { identity.ConfigureUser(user => { user.AddOrUpdateProperty( "DepartmentId", property => { property.UI.Lookup.Url = "/api/departments"; property.UI.Lookup.DisplayPropertyName = "name"; } ); }); }); ```` `UI.Lookup.Url` option takes a URL to get list of departments to select on edit/create forms. This endpoint can be a typical controller, an [auto API controller](API/Auto-API-Controllers.md) or any type of endpoint that returns a proper JSON response. An example implementation that returns a fixed list of departments (in real life, you get the list from a data source): ````csharp [Route("api/departments")] public class DepartmentController : AbpController { [HttpGet] public async Task> GetAsync() { return new ListResultDto( new[] { new DepartmentDto { Id = Guid.Parse("6267f0df-870f-4173-be44-d74b4b56d2bd"), Name = "Human Resources" }, new DepartmentDto { Id = Guid.Parse("21c7b61f-330c-489e-8b8c-80e0a78a5cc5"), Name = "Production" } } ); } } ```` This API returns such a JSON response: ````json { "items": [{ "id": "6267f0df-870f-4173-be44-d74b4b56d2bd", "name": "Human Resources" }, { "id": "21c7b61f-330c-489e-8b8c-80e0a78a5cc5", "name": "Production" }] } ```` ABP can now show an auto-complete select component to pick the department while creating or editing a user: ![extension-navigation-property-form](images/extension-navigation-property-form.png) And shows the department name on the data table: ![extension-navigation-property-form](images/extension-navigation-property-table.png) #### Lookup Options `UI.Lookup` has the following options to customize how to read the response returned from the `Url`: * `Url`: The endpoint to get the list of target entities. This is used on edit and create forms. * `DisplayPropertyName`: The property in the JSON response to read the display name of the target entity to show on the UI. Default: `text`. * `ValuePropertyName`: The property in the JSON response to read the Id of the target entity. Default: `id`. * `FilterParamName`: ABP allows to search/filter the entity list on edit/create forms. This is especially useful if the target list contains a lot of items. In this case, you can return a limited list (top 100, for example) and allow user to search on the list. ABP sends filter text to the server (as a simple query string) with the name of this option. Default: `filter`. * `ResultListPropertyName`: By default, returned JSON result should contain the entity list in an `items` array. You can change the name of this field. Default: `items`. #### Lookup Properties: How Display Name Works? You may wonder how ABP shows the department name on the data table above. It is easy to understand how to fill the dropdown on edit and create forms: ABP makes an AJAX request to the given URL. It re-requests whenever user types to filter the items. However, for the data table, multiple items are shown on the UI and performing a separate AJAX call to get display name of the department for each row would not be so efficient. Instead, the display name of the foreign entity is also saved as an extra property of the entity (see *Extra Properties* section of the [Entities](Entities.md) document) in addition to Id of the foreign entity. If you check the database, you can see the `DepartmentId_Text` in the `ExtraProperties` field in the database table: ````json {"DepartmentId":"21c7b61f-330c-489e-8b8c-80e0a78a5cc5","DepartmentId_Text":"Production"} ```` So, this is a type of *data duplication*. If your target entity's name changes in the database later, there is no automatic synchronization system. The system works as expected, but you see the old name on the data tables. If that's a problem for you, you should care yourself to update this information when display name of your entity changes. ## Database Mapping For relational databases, all extension property values are stored in a single field in the table: ![add-new-propert-to-user-database-extra-properties](images/add-new-propert-to-user-database-extra-properties.png) `ExtraProperties` field stores the properties as a JSON object. While that's fine for some scenarios, you may want to create a dedicated field for your new property. Fortunately, it is very easy to configure. If you are using the Entity Framework Core database provider, you can configure the database mapping as shown below: ````csharp ObjectExtensionManager.Instance .MapEfCoreProperty( "SocialSecurityNumber", (entityBuilder, propertyBuilder) => { propertyBuilder.HasMaxLength(64); } ); ```` Write this inside the `YourProjectNameEfCoreEntityExtensionMappings` class in your `.EntityFrameworkCore` project. Then you need to use the standard `Add-Migration` and `Update-Database` commands to create a new database migration and apply the change to your database. Add-Migration create a new migration as shown below: ````csharp public partial class Added_SocialSecurityNumber_To_IdentityUser : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( name: "SocialSecurityNumber", table: "AbpUsers", maxLength: 128, nullable: true); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( name: "SocialSecurityNumber", table: "AbpUsers"); } } ```` Once you update your database, you will see that the `AbpUsers` table has the new property as a standard table field: ![add-new-propert-to-user-database-extra-properties](images/add-new-propert-to-user-database-field.png) > If you first created a property without a database table field, then you later needed to move this property to a database table field, it is suggested to execute an SQL command in your migration to copy the old values to the new field. > > However, if you don't make it, the ABP Framework seamlessly manages it. It uses the new database field, but fallbacks to the `ExtraProperties` field if it is null. When you save the entity, it moves the value to the new field. See the [Extending Entities](Customizing-Application-Modules-Extending-Entities.md) document for more. ## More See the [Customizing the Modules](Customizing-Application-Modules-Guide.md) guide for an overall index for all the extensibility options. Here, a few things you can do: * You can create a second entity that maps to the same database table with the extra property as a standard class property (if you've defined the EF Core mapping). For the example above, you can add a `public string SocialSecurityNumber {get; set;}` property to the `AppUser` entity in your application, since the `AppUser` entity is mapped to the same `AbpUser` table. Do this only if you need it, since it brings more complexity to your application. * You can override a domain or application service to perform custom logics with your new property. * You can low level control how to add/render a field in the data table on the UI.