mirror of https://github.com/abpframework/abp.git
committed by
GitHub
7 changed files with 304 additions and 15 deletions
@ -1,3 +0,0 @@ |
|||
## AutoMapper Integration |
|||
|
|||
TODO |
|||
@ -1,3 +1,267 @@ |
|||
# Object Extensions |
|||
|
|||
TODO |
|||
ABP Framework provides an **object extension system** to allow you to **add extra properties** to an existing object **without modifying** the related class. This allows to extend functionalities implemented by a depended [application module](Modules/Index.md), especially when you want to [extend entities](Customizing-Application-Modules-Extending-Entities.md) and [DTOs](Customizing-Application-Modules-Overriding-Services.md) defined by the module. |
|||
|
|||
> Object extension system is not normally not needed for your own objects since you can easily add regular properties to your own classes. |
|||
|
|||
## IHasExtraProperties Interface |
|||
|
|||
This is the interface to make a class extensible. It simply defines a `Dictionary` property: |
|||
|
|||
````csharp |
|||
Dictionary<string, object> ExtraProperties { get; } |
|||
```` |
|||
|
|||
Then you can add or get extra properties using this dictionary. |
|||
|
|||
### Base Classes |
|||
|
|||
`IHasExtraProperties` interface is implemented by several base classes by default: |
|||
|
|||
* Implemented by the `AggregateRoot` class (see [entities](Entities.md)). |
|||
* Implemented by `ExtensibleEntityDto`, `ExtensibleAuditedEntityDto`... base [DTO](Data-Transfer-Objects.md) classes. |
|||
* Implemented by the `ExtensibleObject`, which is a simple base class can be inherited for any type of object. |
|||
|
|||
So, if you inherit from these classes, your class will also be extensible. If not, you can always implement it manually. |
|||
|
|||
### Fundamental Extension Methods |
|||
|
|||
While you can directly use the `ExtraProperties` property of a class, it is suggested to use the following extension methods while working with the extra properties. |
|||
|
|||
#### SetProperty |
|||
|
|||
Used to set the value of an extra property: |
|||
|
|||
````csharp |
|||
user.SetProperty("Title", "My Title"); |
|||
user.SetProperty("IsSuperUser", true); |
|||
```` |
|||
|
|||
`SetProperty` returns the same object, so you can chain it: |
|||
|
|||
````csharp |
|||
user.SetProperty("Title", "My Title") |
|||
.SetProperty("IsSuperUser", true); |
|||
```` |
|||
|
|||
#### GetProperty |
|||
|
|||
Used to read the value of an extra property: |
|||
|
|||
````csharp |
|||
var title = user.GetProperty<string>("Title"); |
|||
|
|||
if (user.GetProperty<bool>("IsSuperUser")) |
|||
{ |
|||
//... |
|||
} |
|||
```` |
|||
|
|||
* `GetProperty` is a generic method and takes the object type as the generic parameter. |
|||
* Returns the default value if given property was not set before (default value is `0` for `int`, `false` for `bool`... etc). |
|||
|
|||
##### Non Primitive Property Types |
|||
|
|||
If your property type is not a primitive (int, bool, enum, string... etc) type, then you need to use non-generic version of the `GetProperty` which returns an `object`. |
|||
|
|||
#### HasProperty |
|||
|
|||
Used to check if the object has a property set before. |
|||
|
|||
#### RemoveProperty |
|||
|
|||
Used to remove a property from the object. Use this methods instead of setting a `null` value for the property. |
|||
|
|||
### Some Best Practices |
|||
|
|||
Using magic strings for the property names is dangerous since you can easily type the property name wrong - it is not type safe. Instead; |
|||
|
|||
* Define a constant for your extra property names |
|||
* Create extension methods to easily set your extra properties. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
public static class IdentityUserExtensions |
|||
{ |
|||
private const string TitlePropertyName = "Title"; |
|||
|
|||
public static void SetTitle(this IdentityUser user, string title) |
|||
{ |
|||
user.SetProperty(TitlePropertyName, title); |
|||
} |
|||
|
|||
public static string GetTitle(this IdentityUser user) |
|||
{ |
|||
return user.GetProperty<string>(TitlePropertyName); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Then you can easily set or get the `Title` property: |
|||
|
|||
````csharp |
|||
user.SetTitle("My Title"); |
|||
var title = user.GetTitle(); |
|||
```` |
|||
|
|||
## Object Extension Manager |
|||
|
|||
While you can set arbitrary properties to an extensible object (which implements the `IHasExtraProperties` interface), `ObjectExtensionManager` is used to explicitly define extra properties for extensible classes. |
|||
|
|||
Explicitly defining an extra property has some use cases: |
|||
|
|||
* Allows to control how the extra property is handled on object to object mapping (see the section below). |
|||
* Allows to define metadata for the property. For example, you can map an extra property to a table field in the database while using the [EF Core](Entity-Framework-Core.md). |
|||
|
|||
> `ObjectExtensionManager` implements the singleton pattern (`ObjectExtensionManager.Instance`) and you should define object extensions before your application startup. The [application startup template](Startup-Templates/Application.md) has some pre-defined static classes to safely define object extensions inside. |
|||
|
|||
### AddOrUpdate |
|||
|
|||
`AddOrUpdate` is the main method to define a extra properties or update extra properties for an object. |
|||
|
|||
Example: Define extra properties for the `IdentityUser` entity: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdate<IdentityUser>(options => |
|||
{ |
|||
options.AddOrUpdateProperty<string>("SocialSecurityNumber"); |
|||
options.AddOrUpdateProperty<bool>("IsSuperUser"); |
|||
} |
|||
); |
|||
```` |
|||
|
|||
### AddOrUpdateProperty |
|||
|
|||
While `AddOrUpdateProperty` can be used on the `options` as shown before, if you want to define a single extra property, you can use the shortcut extension method too: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>("SocialSecurityNumber"); |
|||
```` |
|||
|
|||
Sometimes it would be practical to define a single extra property to multiple types. Instead of defining one by one, you can use the following code: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<string>( |
|||
new[] |
|||
{ |
|||
typeof(IdentityUserDto), |
|||
typeof(IdentityUserCreateDto), |
|||
typeof(IdentityUserUpdateDto) |
|||
}, |
|||
"SocialSecurityNumber" |
|||
); |
|||
```` |
|||
|
|||
#### Property Configuration |
|||
|
|||
`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition. |
|||
|
|||
Example: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.CheckPairDefinitionOnMapping = false; |
|||
}); |
|||
```` |
|||
|
|||
> See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option. |
|||
|
|||
`options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document. |
|||
|
|||
## Object to Object Mapping |
|||
|
|||
Assume that you've added an extra property to an extensible entity object and used auto [object to object mapping](Object-To-Object-Mapping.md) to map this entity to an extensible DTO class. You need to be careful in such a case, because the extra property may contain a **sensitive data** that should not be available to clients. |
|||
|
|||
This section offers some **good practices** to control your extra properties on object mapping. |
|||
|
|||
### MapExtraPropertiesTo |
|||
|
|||
`MapExtraPropertiesTo` is an extension method provided by the ABP Framework to copy extra properties from an object to another in a controlled manner. Example usage: |
|||
|
|||
````csharp |
|||
identityUser.MapExtraPropertiesTo(identityUserDto); |
|||
```` |
|||
|
|||
`MapExtraPropertiesTo` **requires to define properties** (as described above) in **both sides** (`IdentityUser` and `IdentityUserDto` in this case) in order to copy the value to the target object. Otherwise, it doesn't copy the value even if it does exists in the source object (`identityUser` in this example). There are some ways to overload this restriction. |
|||
|
|||
#### MappingPropertyDefinitionChecks |
|||
|
|||
`MapExtraPropertiesTo` gets an additional parameter to control the definition check for a single mapping operation: |
|||
|
|||
````csharp |
|||
identityUser.MapExtraPropertiesTo( |
|||
identityUserDto, |
|||
MappingPropertyDefinitionChecks.None |
|||
); |
|||
```` |
|||
|
|||
> Be careful since `MappingPropertyDefinitionChecks.None` copies all extra properties without any check. `MappingPropertyDefinitionChecks` enum has other members too. |
|||
|
|||
If you want to completely disable definition check for a property, you can do it while defining the extra property (or update an existing definition) as shown below: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.CheckPairDefinitionOnMapping = false; |
|||
}); |
|||
```` |
|||
|
|||
#### Ignored Properties |
|||
|
|||
You may want to ignore some properties on a specific mapping operation: |
|||
|
|||
````csharp |
|||
identityUser.MapExtraPropertiesTo( |
|||
identityUserDto, |
|||
ignoredProperties: new[] {"MySensitiveProp"} |
|||
); |
|||
```` |
|||
|
|||
Ignored properties are not copied to the target object. |
|||
|
|||
#### AutoMapper Integration |
|||
|
|||
If you're using the [AutoMapper](https://automapper.org/) library, the ABP Framework also provides an extension method to utilize the `MapExtraPropertiesTo` method defined above. |
|||
|
|||
You can use the `MapExtraProperties()` method inside your mapping profile. |
|||
|
|||
````csharp |
|||
public class MyProfile : Profile |
|||
{ |
|||
public MyProfile() |
|||
{ |
|||
CreateMap<IdentityUser, IdentityUserDto>() |
|||
.MapExtraProperties(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
It has the same parameters with the `MapExtraPropertiesTo` method. |
|||
|
|||
## Entity Framework Core Database Mapping |
|||
|
|||
If you're using the EF Core, you can map an extra property to a table field in the database. Example: |
|||
|
|||
````csharp |
|||
ObjectExtensionManager.Instance |
|||
.AddOrUpdateProperty<IdentityUser, string>( |
|||
"SocialSecurityNumber", |
|||
options => |
|||
{ |
|||
options.MapEfCore(b => b.HasMaxLength(32)); |
|||
} |
|||
); |
|||
```` |
|||
|
|||
See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more. |
|||
Loading…
Reference in new issue