对象扩展
ABP提供了一套对象扩展系统,允许您在不修改相关类的情况下为现有对象添加额外属性。这使得您可以扩展由依赖应用模块实现的功能,特别是当您需要扩展实体和模块定义的DTO时。
对于您自己的对象通常不需要使用对象扩展系统,因为您可以轻松地为自己定义的类添加常规属性。
IHasExtraProperties 接口
这是使类可扩展的接口。它简单地定义了一个Dictionary属性:
ExtraPropertyDictionary ExtraProperties { get; }
ExtraPropertyDictionary类继承自Dictionary<string, object>类。您可以使用此字典添加或获取扩展属性。
基类
IHasExtraProperties接口默认由多个基类实现:
- 由
AggregateRoot类实现(参见实体) - 由
ExtensibleEntityDto、ExtensibleAuditedEntityDto等基础DTO类实现 - 由
ExtensibleObject实现,这是一个简单基类,可被任何类型的对象继承
因此,如果您继承这些类,您的类也将是可扩展的。如果没有,您可以随时手动实现它。
基础扩展方法
虽然您可以直接使用类的ExtraProperties属性,但建议在处理扩展属性时使用以下扩展方法。
SetProperty
用于设置扩展属性的值:
user.SetProperty("Title", "我的标题");
user.SetProperty("IsSuperUser", true);
SetProperty返回同一对象,因此您可以链式调用:
user.SetProperty("Title", "我的标题")
.SetProperty("IsSuperUser", true);
GetProperty
用于读取扩展属性的值:
var title = user.GetProperty<string>("Title");
if (user.GetProperty<bool>("IsSuperUser"))
{
//...
}
GetProperty是一个泛型方法,将对象类型作为泛型参数- 如果之前未设置给定属性,则返回默认值(
int的默认值为0,bool的默认值为false等)
非原始属性类型
如果属性类型不是原始类型(int、bool、enum、string等),则需要使用非泛型版本的GetProperty,它返回一个object。
HasProperty
用于检查对象之前是否设置了某个属性。
RemoveProperty
用于从对象中移除属性。使用此方法而不是为属性设置null值。
一些最佳实践
对属性名称使用魔术字符串是危险的,因为您很容易输错属性名称——这不是类型安全的。相反:
- 为扩展属性名称定义一个常量
- 创建扩展方法以轻松设置扩展属性
示例:
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);
}
}
然后您可以轻松设置或获取Title属性:
user.SetTitle("我的标题");
var title = user.GetTitle();
对象扩展管理器
虽然您可以为可扩展对象(实现IHasExtraProperties接口的对象)设置任意属性,但ObjectExtensionManager用于显式定义可扩展类的扩展属性。
显式定义扩展属性有一些用例:
- 允许控制对象到对象映射时如何处理扩展属性(参见下文)
- 允许为属性定义元数据。例如,在使用EF Core时,您可以将扩展属性映射到数据库表字段
ObjectExtensionManager实现了单例模式(ObjectExtensionManager.Instance),您应在应用程序启动前定义对象扩展。应用程序启动模板提供了一些预定义的静态类,用于安全地在内部定义对象扩展。
AddOrUpdate
AddOrUpdate是定义或更新对象扩展属性的主要方法。
示例:为IdentityUser实体定义扩展属性:
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUser>(options =>
{
options.AddOrUpdateProperty<string>("SocialSecurityNumber");
options.AddOrUpdateProperty<bool>("IsSuperUser");
}
);
AddOrUpdateProperty
虽然可以在options上使用AddOrUpdateProperty,但如果您想定义单个扩展属性,也可以使用快捷扩展方法:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>("SocialSecurityNumber");
有时将单个扩展属性定义到多个类型会很实用。您可以逐个定义,也可以使用以下代码:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
属性配置
AddOrUpdateProperty还可以获取一个操作,用于对属性定义执行额外配置:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
//配置选项...
});
options有一个名为Configuration的字典,使对象扩展定义更具可扩展性。EF Core使用它将扩展属性映射到数据库表字段。请参阅扩展实体文档。
以下部分解释了基本的属性配置选项。
默认值
新属性会自动设置默认值,这是属性类型的自然默认值,如string为null,bool为false,int为0。
有两种方法可以覆盖默认值:
DefaultValue 选项
DefaultValue选项可以设置为任何值:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, int>(
"MyIntProperty",
options =>
{
options.DefaultValue = 42;
});
DefaultValueFactory 选项
DefaultValueFactory可以设置为返回默认值的函数:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, DateTime>(
"MyDateTimeProperty",
options =>
{
options.DefaultValueFactory = () => DateTime.Now;
});
options.DefaultValueFactory的优先级高于options.DefaultValue。
提示:仅当默认值可能随时间变化(如此示例中的
DateTime.Now)时使用DefaultValueFactory选项。如果是常量值,请使用DefaultValue选项。
CheckPairDefinitionOnMapping
控制在映射两个可扩展对象时如何检查属性定义。请参阅“对象到对象映射”部分以更好地理解CheckPairDefinitionOnMapping选项。
验证
您可能希望为已定义的扩展属性添加一些验证规则。AddOrUpdateProperty方法选项允许两种验证方式:
- 可以为属性添加数据注解属性
- 可以编写一个操作(代码块)来执行自定义验证
当您在自动验证的方法(例如控制器操作、页面处理程序方法、应用服务方法等)中使用对象时,验证会生效。因此,每当扩展对象被验证时,所有扩展属性都会被验证。
数据注解属性
所有标准数据注解属性都适用于扩展属性。示例:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Attributes.Add(new RequiredAttribute());
options.Attributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
);
});
通过此配置,如果没有提供有效的SocialSecurityNumber值,IdentityUserCreateDto对象将无效。
默认验证属性
创建某些类型的属性时,会自动添加一些属性:
- 对于不可为空的原始属性类型(例如
int、bool、DateTime等)和enum类型,会添加RequiredAttribute - 对于枚举类型,会添加
EnumDataTypeAttribute以防止设置无效的枚举值
如果您不需要这些属性,请使用options.Attributes.Clear();。
自定义验证
如果需要,可以添加一个自定义操作来验证扩展属性。示例:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Validators.Add(context =>
{
var socialSecurityNumber = context.Value as string;
if (socialSecurityNumber == null ||
socialSecurityNumber.StartsWith("X"))
{
context.ValidationErrors.Add(
new ValidationResult(
"无效的社会安全号码: " + socialSecurityNumber,
new[] { "SocialSecurityNumber" }
)
);
}
});
});
context.ServiceProvider可用于解析服务依赖以处理高级场景。
除了为单个属性添加自定义验证逻辑外,您还可以添加在对象级别执行的自定义验证逻辑。示例:
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUserCreateDto>(objConfig =>
{
//定义两个属性及其验证规则
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
{
propertyConfig.Attributes.Add(new RequiredAttribute());
});
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
{
propertyConfig.Attributes.Add(new RequiredAttribute());
});
//编写适用于多个属性的通用验证逻辑
objConfig.Validators.Add(context =>
{
if (context.ValidatingObject.GetProperty<string>("Password") !=
context.ValidatingObject.GetProperty<string>("PasswordRepeat"))
{
context.ValidationErrors.Add(
new ValidationResult(
"请重复相同的密码!",
new[] { "Password", "PasswordRepeat" }
)
);
}
});
});
对象到对象映射
假设您已向可扩展实体对象添加了扩展属性,并使用自动对象到对象映射将此实体映射到可扩展DTO类。在这种情况下您需要小心,因为扩展属性可能包含不应向客户端提供的敏感数据。
本节提供了一些良好实践来控制对象映射时的扩展属性。
MapExtraPropertiesTo
MapExtraPropertiesTo是ABP提供的一个扩展方法,用于以受控方式将扩展属性从一个对象复制到另一个对象。示例用法:
identityUser.MapExtraPropertiesTo(identityUserDto);
MapExtraPropertiesTo需要在双方(此示例中为IdentityUser和IdentityUserDto)定义属性(如上所述)才能将值复制到目标对象。否则,即使源对象(此示例中的identityUser)中存在该值,也不会复制。有一些方法可以重写此限制。
MappingPropertyDefinitionChecks
MapExtraPropertiesTo获取一个额外参数来控制单个映射操作的定义检查:
identityUser.MapExtraPropertiesTo(
identityUserDto,
MappingPropertyDefinitionChecks.None
);
注意,
MappingPropertyDefinitionChecks.None会在没有任何检查的情况下复制所有扩展属性。MappingPropertyDefinitionChecks枚举还有其他成员。
如果希望完全禁用属性的定义检查,可以在定义扩展属性(或更新现有定义)时执行此操作,如下所示:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.CheckPairDefinitionOnMapping = false;
});
忽略属性
您可能希望在特定映射操作中忽略某些属性:
identityUser.MapExtraPropertiesTo(
identityUserDto,
ignoredProperties: new[] {"MySensitiveProp"}
);
忽略的属性不会复制到目标对象。
AutoMapper 集成
如果您使用AutoMapper库,ABP还提供了一个扩展方法来利用上述MapExtraPropertiesTo方法。
您可以在映射配置文件中使用MapExtraProperties()方法。
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<IdentityUser, IdentityUserDto>()
.MapExtraProperties();
}
}
它具有与MapExtraPropertiesTo方法相同的参数。
Mapperly 集成
如果您使用Mapperly库,ABP还提供了一个扩展方法来利用上述MapExtraPropertiesTo方法。
您可以使用MapExtraProperties属性到Mapperly类:
[Mapper]
[MapExtraProperties]
public partial class IdentityUserToProfileDtoMapper : MapperBase<IdentityUser, IdentityUserDto>
{
public override partial IdentityUserDto Map(IdentityUser source);
public override partial void Map(IdentityUser source, IdentityUserDto destination);
}
Entity Framework Core 数据库映射
如果您使用EF Core,可以将扩展属性映射到数据库表字段。示例:
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
}
);
有关更多信息,请参阅Entity Framework Core 集成文档。
抠丁客


