mirror of https://github.com/abpframework/abp.git
committed by
GitHub
80 changed files with 1545 additions and 273 deletions
@ -0,0 +1,3 @@ |
|||||
|
# Object Extensions |
||||
|
|
||||
|
TODO |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.ObjectExtending; |
||||
|
|
||||
|
namespace AutoMapper |
||||
|
{ |
||||
|
public static class AbpAutoMapperExtensibleDtoExtensions |
||||
|
{ |
||||
|
public static IMappingExpression<TSource, TDestination> MapExtraProperties<TSource, TDestination>( |
||||
|
this IMappingExpression<TSource, TDestination> mappingExpression, |
||||
|
MappingPropertyDefinitionChecks definitionChecks = MappingPropertyDefinitionChecks.Both) |
||||
|
where TDestination : IHasExtraProperties |
||||
|
where TSource : IHasExtraProperties |
||||
|
{ |
||||
|
return mappingExpression |
||||
|
.ForMember( |
||||
|
x => x.ExtraProperties, |
||||
|
y => y.MapFrom( |
||||
|
(source, destination, extraProps) => |
||||
|
{ |
||||
|
var result = extraProps.IsNullOrEmpty() |
||||
|
? new Dictionary<string, object>() |
||||
|
: new Dictionary<string, object>(extraProps); |
||||
|
|
||||
|
HasExtraPropertiesObjectExtendingExtensions |
||||
|
.MapExtraPropertiesTo<TSource, TDestination>( |
||||
|
source.ExtraProperties, |
||||
|
result |
||||
|
); |
||||
|
|
||||
|
return result; |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.Application.Dtos |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleAuditedEntityDto<TPrimaryKey> : ExtensibleCreationAuditedEntityDto<TPrimaryKey>, IAuditedObject |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public DateTime? LastModificationTime { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Guid? LastModifierId { get; set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleAuditedEntityDto : ExtensibleCreationAuditedEntityDto, IAuditedObject |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public DateTime? LastModificationTime { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Guid? LastModifierId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.Application.Dtos |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
|
||||
|
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
|
||||
|
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleAuditedEntityDto<TPrimaryKey>, IAuditedObject<TUserDto> |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto Creator { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto LastModifier { get; set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
|
||||
|
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleAuditedEntityWithUserDto<TUserDto> : ExtensibleAuditedEntityDto, |
||||
|
IAuditedObject<TUserDto> |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto Creator { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto LastModifier { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.Application.Dtos |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject"/> interface.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleCreationAuditedEntityDto<TPrimaryKey> : ExtensibleEntityDto<TPrimaryKey>, ICreationAuditedObject |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public DateTime CreationTime { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Guid? CreatorId { get; set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject"/> interface.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleCreationAuditedEntityDto : ExtensibleEntityDto, ICreationAuditedObject |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public DateTime CreationTime { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Guid? CreatorId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.Application.Dtos |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
|
||||
|
/// It has the <see cref="Creator"/> object as a DTO represents the user.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
|
||||
|
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleCreationAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleCreationAuditedEntityDto<TPrimaryKey>, ICreationAuditedObject<TUserDto> |
||||
|
{ |
||||
|
public TUserDto Creator { get; set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
|
||||
|
/// It has the <see cref="Creator"/> object as a DTO represents the user.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleCreationAuditedEntityWithUserDto<TUserDto> : ExtensibleCreationAuditedEntityDto, |
||||
|
ICreationAuditedObject<TUserDto> |
||||
|
{ |
||||
|
public TUserDto Creator { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.ObjectExtending; |
||||
|
|
||||
|
namespace Volo.Abp.Application.Dtos |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleEntityDto<TKey> : ExtensibleObject, IEntityDto<TKey> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Id of the entity.
|
||||
|
/// </summary>
|
||||
|
public TKey Id { get; set; } |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return $"[DTO: {GetType().Name}] Id = {Id}"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto |
||||
|
{ |
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return $"[DTO: {GetType().Name}]"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.Application.Dtos |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject"/> interface.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleFullAuditedEntityDto<TPrimaryKey> : ExtensibleAuditedEntityDto<TPrimaryKey>, IFullAuditedObject |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public bool IsDeleted { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Guid? DeleterId { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public DateTime? DeletionTime { get; set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject"/> interface.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleFullAuditedEntityDto : ExtensibleAuditedEntityDto, IFullAuditedObject |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public bool IsDeleted { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Guid? DeleterId { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public DateTime? DeletionTime { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.Application.Dtos |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject{TUser}"/> interface.
|
||||
|
/// It has the <see cref="Creator"/>, <see cref="LastModifier"/> and <see cref="Deleter"/> objects as a DTOs represent the related user.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
|
||||
|
/// <typeparam name="TUserDto">Type of the User</typeparam>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleFullAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleFullAuditedEntityDto<TPrimaryKey>, IFullAuditedObject<TUserDto> |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto Creator { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto LastModifier { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto Deleter { get; set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject{TUser}"/> interface.
|
||||
|
/// It has the <see cref="Creator"/>, <see cref="LastModifier"/> and <see cref="Deleter"/> objects as a DTOs represent the related user.
|
||||
|
/// It also implements the <see cref="IHasExtraProperties"/> interface.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TUserDto">Type of the User</typeparam>
|
||||
|
[Serializable] |
||||
|
public abstract class ExtensibleFullAuditedEntityWithUserDto<TUserDto> : ExtensibleFullAuditedEntityDto, |
||||
|
IFullAuditedObject<TUserDto> |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto Creator { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto LastModifier { get; set; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public TUserDto Deleter { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -1,14 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
|
|
||||
namespace Volo.Abp.EntityFrameworkCore.Extensions |
|
||||
{ |
|
||||
public class EntityExtensionInfo |
|
||||
{ |
|
||||
public Dictionary<string, PropertyExtensionInfo> Properties { get; set; } |
|
||||
|
|
||||
public EntityExtensionInfo() |
|
||||
{ |
|
||||
Properties = new Dictionary<string, PropertyExtensionInfo>(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,133 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using JetBrains.Annotations; |
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
|
||||
using Volo.Abp.Data; |
|
||||
|
|
||||
namespace Volo.Abp.EntityFrameworkCore.Extensions |
|
||||
{ |
|
||||
public static class EntityExtensionManager |
|
||||
{ |
|
||||
private static readonly Dictionary<Type, EntityExtensionInfo> ExtensionInfos; |
|
||||
|
|
||||
static EntityExtensionManager() |
|
||||
{ |
|
||||
ExtensionInfos = new Dictionary<Type, EntityExtensionInfo>(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Adds an extension property for an entity.
|
|
||||
/// If it is already added, replaces the <paramref name="propertyBuildAction"/>
|
|
||||
/// by the given one!
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEntity">Type of the entity</typeparam>
|
|
||||
/// <typeparam name="TProperty">Type of the new property</typeparam>
|
|
||||
/// <param name="propertyName">Name of the property</param>
|
|
||||
/// <param name="propertyBuildAction">An action to configure the database mapping for the new property</param>
|
|
||||
public static void AddProperty<TEntity, TProperty>( |
|
||||
[NotNull]string propertyName, |
|
||||
[NotNull]Action<PropertyBuilder> propertyBuildAction) |
|
||||
{ |
|
||||
AddProperty( |
|
||||
typeof(TEntity), |
|
||||
typeof(TProperty), |
|
||||
propertyName, |
|
||||
propertyBuildAction |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Adds an extension property for an entity.
|
|
||||
/// If it is already added, replaces the <paramref name="propertyBuildAction"/>
|
|
||||
/// by the given one!
|
|
||||
/// </summary>
|
|
||||
/// <param name="entityType">Type of the entity</param>
|
|
||||
/// <param name="propertyType">Type of the new property</param>
|
|
||||
/// <param name="propertyName">Name of the property</param>
|
|
||||
/// <param name="propertyBuildAction">An action to configure the database mapping for the new property</param>
|
|
||||
public static void AddProperty( |
|
||||
Type entityType, |
|
||||
Type propertyType, |
|
||||
[NotNull]string propertyName, |
|
||||
[NotNull]Action<PropertyBuilder> propertyBuildAction) |
|
||||
{ |
|
||||
Check.NotNull(entityType, nameof(entityType)); |
|
||||
Check.NotNull(propertyType, nameof(propertyType)); |
|
||||
Check.NotNullOrWhiteSpace(propertyName, nameof(propertyName)); |
|
||||
Check.NotNull(propertyBuildAction, nameof(propertyBuildAction)); |
|
||||
|
|
||||
var extensionInfo = ExtensionInfos |
|
||||
.GetOrAdd(entityType, () => new EntityExtensionInfo()); |
|
||||
|
|
||||
var propertyExtensionInfo = extensionInfo.Properties |
|
||||
.GetOrAdd(propertyName, () => new PropertyExtensionInfo(propertyType)); |
|
||||
|
|
||||
propertyExtensionInfo.Action = propertyBuildAction; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Configures the entity mapping for the defined extensions.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEntity">The entity tye</typeparam>
|
|
||||
/// <param name="entityTypeBuilder">Entity type builder</param>
|
|
||||
public static void ConfigureExtensions<TEntity>( |
|
||||
[NotNull] this EntityTypeBuilder<TEntity> entityTypeBuilder) |
|
||||
where TEntity : class, IHasExtraProperties |
|
||||
{ |
|
||||
ConfigureExtensions(typeof(TEntity), entityTypeBuilder); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Configures the entity mapping for the defined extensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="entityType">Type of the entity</param>
|
|
||||
/// <param name="entityTypeBuilder">Entity type builder</param>
|
|
||||
public static void ConfigureExtensions( |
|
||||
[NotNull] Type entityType, |
|
||||
[NotNull] EntityTypeBuilder entityTypeBuilder) |
|
||||
{ |
|
||||
Check.NotNull(entityType, nameof(entityType)); |
|
||||
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); |
|
||||
|
|
||||
var entityExtensionInfo = ExtensionInfos.GetOrDefault(entityType); |
|
||||
if (entityExtensionInfo == null) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
foreach (var propertyExtensionInfo in entityExtensionInfo.Properties) |
|
||||
{ |
|
||||
var propertyName = propertyExtensionInfo.Key; |
|
||||
var propertyType = propertyExtensionInfo.Value.PropertyType; |
|
||||
|
|
||||
/* Prevent multiple calls to the entityTypeBuilder.Property(...) method */ |
|
||||
if (entityTypeBuilder.Metadata.FindProperty(propertyName) != null) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
var property = entityTypeBuilder.Property( |
|
||||
propertyType, |
|
||||
propertyName |
|
||||
); |
|
||||
|
|
||||
propertyExtensionInfo.Value.Action(property); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static string[] GetPropertyNames(Type entityType) |
|
||||
{ |
|
||||
var entityExtensionInfo = ExtensionInfos.GetOrDefault(entityType); |
|
||||
if (entityExtensionInfo == null) |
|
||||
{ |
|
||||
return Array.Empty<string>(); |
|
||||
} |
|
||||
|
|
||||
return entityExtensionInfo |
|
||||
.Properties |
|
||||
.Select(p => p.Key) |
|
||||
.ToArray(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,17 +0,0 @@ |
|||||
using System; |
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
|
||||
|
|
||||
namespace Volo.Abp.EntityFrameworkCore.Extensions |
|
||||
{ |
|
||||
public class PropertyExtensionInfo |
|
||||
{ |
|
||||
public Action<PropertyBuilder> Action { get; set; } |
|
||||
|
|
||||
public Type PropertyType { get; } |
|
||||
|
|
||||
public PropertyExtensionInfo(Type propertyType) |
|
||||
{ |
|
||||
PropertyType = propertyType; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public static class EfCoreObjectExtensionInfoExtensions |
||||
|
{ |
||||
|
public static ObjectExtensionInfo MapEfCoreProperty<TProperty>( |
||||
|
[NotNull] this ObjectExtensionInfo objectExtensionInfo, |
||||
|
[NotNull] string propertyName, |
||||
|
[CanBeNull] Action<PropertyBuilder> propertyBuildAction) |
||||
|
{ |
||||
|
return objectExtensionInfo.MapEfCoreProperty( |
||||
|
typeof(TProperty), |
||||
|
propertyName, |
||||
|
propertyBuildAction |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public static ObjectExtensionInfo MapEfCoreProperty( |
||||
|
[NotNull] this ObjectExtensionInfo objectExtensionInfo, |
||||
|
[NotNull] Type propertyType, |
||||
|
[NotNull] string propertyName, |
||||
|
[CanBeNull] Action<PropertyBuilder> propertyBuildAction) |
||||
|
{ |
||||
|
Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); |
||||
|
|
||||
|
return objectExtensionInfo.AddOrUpdateProperty( |
||||
|
propertyType, |
||||
|
propertyName, |
||||
|
options => |
||||
|
{ |
||||
|
options.MapEfCore( |
||||
|
propertyBuildAction |
||||
|
); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
using System; |
||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public static class EfCoreObjectExtensionManagerExtensions |
||||
|
{ |
||||
|
public static ObjectExtensionManager MapEfCoreProperty<TEntity, TProperty>( |
||||
|
[NotNull] this ObjectExtensionManager objectExtensionManager, |
||||
|
[NotNull] string propertyName, |
||||
|
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null) |
||||
|
where TEntity : IHasExtraProperties, IEntity |
||||
|
{ |
||||
|
return objectExtensionManager.MapEfCoreProperty( |
||||
|
typeof(TEntity), |
||||
|
typeof(TProperty), |
||||
|
propertyName, |
||||
|
propertyBuildAction |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public static ObjectExtensionManager MapEfCoreProperty( |
||||
|
[NotNull] this ObjectExtensionManager objectExtensionManager, |
||||
|
[NotNull] Type entityType, |
||||
|
[NotNull] Type propertyType, |
||||
|
[NotNull] string propertyName, |
||||
|
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null) |
||||
|
{ |
||||
|
Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); |
||||
|
|
||||
|
return objectExtensionManager.AddOrUpdateProperty( |
||||
|
entityType, |
||||
|
propertyType, |
||||
|
propertyName, |
||||
|
options => |
||||
|
{ |
||||
|
options.MapEfCore( |
||||
|
propertyBuildAction |
||||
|
); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public static void ConfigureEfCoreEntity( |
||||
|
[NotNull] this ObjectExtensionManager objectExtensionManager, |
||||
|
[NotNull] EntityTypeBuilder typeBuilder) |
||||
|
{ |
||||
|
Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); |
||||
|
Check.NotNull(typeBuilder, nameof(typeBuilder)); |
||||
|
|
||||
|
var objectExtension = objectExtensionManager.GetOrNull(typeBuilder.Metadata.ClrType); |
||||
|
if (objectExtension == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var property in objectExtension.GetProperties()) |
||||
|
{ |
||||
|
var efCoreMapping = property.GetEfCoreMappingOrNull(); |
||||
|
if (efCoreMapping == null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
/* Prevent multiple calls to the entityTypeBuilder.Property(...) method */ |
||||
|
if (typeBuilder.Metadata.FindProperty(property.Name) != null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var propertyBuilder = typeBuilder.Property(property.Type, property.Name); |
||||
|
|
||||
|
efCoreMapping.PropertyBuildAction?.Invoke(propertyBuilder); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public static class EfCoreObjectExtensionPropertyInfoExtensions |
||||
|
{ |
||||
|
public const string EfCorePropertyConfigurationName = "EfCoreMapping"; |
||||
|
|
||||
|
[NotNull] |
||||
|
public static ObjectExtensionPropertyInfo MapEfCore( |
||||
|
[NotNull] this ObjectExtensionPropertyInfo propertyExtension, |
||||
|
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null) |
||||
|
{ |
||||
|
Check.NotNull(propertyExtension, nameof(propertyExtension)); |
||||
|
|
||||
|
propertyExtension.Configuration[EfCorePropertyConfigurationName] = |
||||
|
new ObjectExtensionPropertyInfoEfCoreMappingOptions( |
||||
|
propertyExtension, |
||||
|
propertyBuildAction |
||||
|
); |
||||
|
|
||||
|
return propertyExtension; |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public static ObjectExtensionPropertyInfoEfCoreMappingOptions GetEfCoreMappingOrNull( |
||||
|
[NotNull] this ObjectExtensionPropertyInfo propertyExtension) |
||||
|
{ |
||||
|
Check.NotNull(propertyExtension, nameof(propertyExtension)); |
||||
|
|
||||
|
return propertyExtension |
||||
|
.Configuration |
||||
|
.GetOrDefault(EfCorePropertyConfigurationName) |
||||
|
as ObjectExtensionPropertyInfoEfCoreMappingOptions; |
||||
|
} |
||||
|
|
||||
|
public static bool IsMappedToFieldForEfCore( |
||||
|
[NotNull] this ObjectExtensionPropertyInfo propertyExtension) |
||||
|
{ |
||||
|
Check.NotNull(propertyExtension, nameof(propertyExtension)); |
||||
|
|
||||
|
return propertyExtension |
||||
|
.Configuration |
||||
|
.ContainsKey(EfCorePropertyConfigurationName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public class ObjectExtensionPropertyInfoEfCoreMappingOptions |
||||
|
{ |
||||
|
[NotNull] |
||||
|
public ObjectExtensionPropertyInfo ExtensionProperty { get; } |
||||
|
|
||||
|
[NotNull] |
||||
|
public ObjectExtensionInfo ObjectExtension => ExtensionProperty.ObjectExtension; |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public Action<PropertyBuilder> PropertyBuildAction { get; set; } |
||||
|
|
||||
|
public ObjectExtensionPropertyInfoEfCoreMappingOptions( |
||||
|
[NotNull] ObjectExtensionPropertyInfo extensionProperty, |
||||
|
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null) |
||||
|
{ |
||||
|
ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty)); |
||||
|
|
||||
|
PropertyBuildAction = propertyBuildAction; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,3 @@ |
|||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
[assembly: InternalsVisibleTo("Volo.Abp.ObjectExtending.Tests")] |
||||
@ -0,0 +1,21 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<AssemblyName>Volo.Abp.ObjectExtending</AssemblyName> |
||||
|
<PackageId>Volo.Abp.ObjectExtending</PackageId> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public class AbpObjectExtendingModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class ExtensibleObject : IHasExtraProperties |
||||
|
{ |
||||
|
public Dictionary<string, object> ExtraProperties { get; protected set; } |
||||
|
|
||||
|
public ExtensibleObject() |
||||
|
{ |
||||
|
ExtraProperties = new Dictionary<string, object>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,175 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public static class HasExtraPropertiesObjectExtendingExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Copies extra properties from the <paramref name="source"/> object
|
||||
|
/// to the <paramref name="destination"/> object.
|
||||
|
///
|
||||
|
/// Checks property definitions (over the <see cref="ObjectExtensionManager"/>)
|
||||
|
/// based on the <paramref name="definitionChecks"/> preference.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TSource">Source class type</typeparam>
|
||||
|
/// <typeparam name="TDestination">Destination class type</typeparam>
|
||||
|
/// <param name="source">The source object</param>
|
||||
|
/// <param name="destination">The destination object</param>
|
||||
|
/// <param name="definitionChecks">
|
||||
|
/// Controls which properties to map.
|
||||
|
/// </param>
|
||||
|
public static void MapExtraPropertiesTo<TSource, TDestination>( |
||||
|
[NotNull] this TSource source, |
||||
|
[NotNull] TDestination destination, |
||||
|
MappingPropertyDefinitionChecks definitionChecks = MappingPropertyDefinitionChecks.Both) |
||||
|
where TSource : IHasExtraProperties |
||||
|
where TDestination : IHasExtraProperties |
||||
|
{ |
||||
|
Check.NotNull(source, nameof(source)); |
||||
|
Check.NotNull(destination, nameof(destination)); |
||||
|
|
||||
|
MapExtraPropertiesTo( |
||||
|
typeof(TSource), |
||||
|
typeof(TDestination), |
||||
|
source.ExtraProperties, |
||||
|
destination.ExtraProperties, |
||||
|
definitionChecks |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copies extra properties from the <paramref name="sourceDictionary"/> object
|
||||
|
/// to the <paramref name="destinationDictionary"/> object.
|
||||
|
///
|
||||
|
/// Checks property definitions (over the <see cref="ObjectExtensionManager"/>)
|
||||
|
/// based on the <paramref name="definitionChecks"/> preference.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TSource">Source class type (for definition check)</typeparam>
|
||||
|
/// <typeparam name="TDestination">Destination class type (for definition check)</typeparam>
|
||||
|
/// <param name="sourceDictionary">The source dictionary object</param>
|
||||
|
/// <param name="destinationDictionary">The destination dictionary object</param>
|
||||
|
/// <param name="definitionChecks">
|
||||
|
/// Controls which properties to map.
|
||||
|
/// </param>
|
||||
|
public static void MapExtraPropertiesTo<TSource, TDestination>( |
||||
|
[NotNull] Dictionary<string, object> sourceDictionary, |
||||
|
[NotNull] Dictionary<string, object> destinationDictionary, |
||||
|
MappingPropertyDefinitionChecks definitionChecks = MappingPropertyDefinitionChecks.Both) |
||||
|
where TSource : IHasExtraProperties |
||||
|
where TDestination : IHasExtraProperties |
||||
|
{ |
||||
|
MapExtraPropertiesTo( |
||||
|
typeof(TSource), |
||||
|
typeof(TDestination), |
||||
|
sourceDictionary, |
||||
|
destinationDictionary, |
||||
|
definitionChecks |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copies extra properties from the <paramref name="sourceDictionary"/> object
|
||||
|
/// to the <paramref name="destinationDictionary"/> object.
|
||||
|
///
|
||||
|
/// Checks property definitions (over the <see cref="ObjectExtensionManager"/>)
|
||||
|
/// based on the <paramref name="definitionChecks"/> preference.
|
||||
|
/// </summary>
|
||||
|
/// <param name="sourceType">Source type (for definition check)</param>
|
||||
|
/// <param name="destinationType">Destination class type (for definition check)</param>
|
||||
|
/// <param name="sourceDictionary">The source dictionary object</param>
|
||||
|
/// <param name="destinationDictionary">The destination dictionary object</param>
|
||||
|
/// <param name="definitionChecks">
|
||||
|
/// Controls which properties to map.
|
||||
|
/// </param>
|
||||
|
public static void MapExtraPropertiesTo( |
||||
|
[NotNull] Type sourceType, |
||||
|
[NotNull] Type destinationType, |
||||
|
[NotNull] Dictionary<string, object> sourceDictionary, |
||||
|
[NotNull] Dictionary<string, object> destinationDictionary, |
||||
|
MappingPropertyDefinitionChecks definitionChecks = MappingPropertyDefinitionChecks.Both) |
||||
|
{ |
||||
|
Check.AssignableTo<IHasExtraProperties>(sourceType, nameof(sourceType)); |
||||
|
Check.AssignableTo<IHasExtraProperties>(destinationType, nameof(destinationType)); |
||||
|
Check.NotNull(sourceDictionary, nameof(sourceDictionary)); |
||||
|
Check.NotNull(destinationDictionary, nameof(destinationDictionary)); |
||||
|
|
||||
|
var sourceObjectExtension = ObjectExtensionManager.Instance.GetOrNull(sourceType); |
||||
|
if (definitionChecks.HasFlag(MappingPropertyDefinitionChecks.Source) && |
||||
|
sourceObjectExtension == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var destinationObjectExtension = ObjectExtensionManager.Instance.GetOrNull(destinationType); |
||||
|
if (definitionChecks.HasFlag(MappingPropertyDefinitionChecks.Destination) && |
||||
|
destinationObjectExtension == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (definitionChecks == MappingPropertyDefinitionChecks.None) |
||||
|
{ |
||||
|
foreach (var keyValue in sourceDictionary) |
||||
|
{ |
||||
|
destinationDictionary[keyValue.Key] = keyValue.Value; |
||||
|
} |
||||
|
} |
||||
|
else if (definitionChecks == MappingPropertyDefinitionChecks.Source) |
||||
|
{ |
||||
|
Debug.Assert(sourceObjectExtension != null, nameof(sourceObjectExtension) + " != null"); |
||||
|
|
||||
|
foreach (var property in sourceObjectExtension.GetProperties()) |
||||
|
{ |
||||
|
if (!sourceDictionary.ContainsKey(property.Name)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
destinationDictionary[property.Name] = sourceDictionary[property.Name]; |
||||
|
} |
||||
|
} |
||||
|
else if (definitionChecks == MappingPropertyDefinitionChecks.Destination) |
||||
|
{ |
||||
|
Debug.Assert(destinationObjectExtension != null, nameof(destinationObjectExtension) + " != null"); |
||||
|
|
||||
|
foreach (var keyValue in sourceDictionary) |
||||
|
{ |
||||
|
if (!destinationObjectExtension.HasProperty(keyValue.Key)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
destinationDictionary[keyValue.Key] = keyValue.Value; |
||||
|
} |
||||
|
} |
||||
|
else if (definitionChecks == MappingPropertyDefinitionChecks.Both) |
||||
|
{ |
||||
|
Debug.Assert(sourceObjectExtension != null, nameof(sourceObjectExtension) + " != null"); |
||||
|
Debug.Assert(destinationObjectExtension != null, nameof(destinationObjectExtension) + " != null"); |
||||
|
|
||||
|
foreach (var property in sourceObjectExtension.GetProperties()) |
||||
|
{ |
||||
|
if (!sourceDictionary.ContainsKey(property.Name)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (!destinationObjectExtension.HasProperty(property.Name)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
destinationDictionary[property.Name] = sourceDictionary[property.Name]; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new NotImplementedException(definitionChecks + " was not implemented!"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
[Flags] |
||||
|
public enum MappingPropertyDefinitionChecks : byte |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// No check. Copy all extra properties from the source to the destination.
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copy the extra properties defined for the source class.
|
||||
|
/// </summary>
|
||||
|
Source = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copy the extra properties defined for the destination class.
|
||||
|
/// </summary>
|
||||
|
Destination = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copy extra properties defined for both of the source and destination classes.
|
||||
|
/// </summary>
|
||||
|
Both = Source | Destination |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public class ObjectExtensionInfo |
||||
|
{ |
||||
|
[NotNull] |
||||
|
public Type Type { get; } |
||||
|
|
||||
|
[NotNull] |
||||
|
protected Dictionary<string, ObjectExtensionPropertyInfo> Properties { get; } |
||||
|
|
||||
|
[NotNull] |
||||
|
public Dictionary<object, object> Configuration { get; } |
||||
|
|
||||
|
public ObjectExtensionInfo([NotNull] Type type) |
||||
|
{ |
||||
|
Type = Check.AssignableTo<IHasExtraProperties>(type, nameof(type)); |
||||
|
Properties = new Dictionary<string, ObjectExtensionPropertyInfo>(); |
||||
|
Configuration = new Dictionary<object, object>(); |
||||
|
} |
||||
|
|
||||
|
public virtual bool HasProperty(string propertyName) |
||||
|
{ |
||||
|
return Properties.ContainsKey(propertyName); |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public virtual ObjectExtensionInfo AddOrUpdateProperty<TProperty>( |
||||
|
[NotNull] string propertyName, |
||||
|
[CanBeNull] Action<ObjectExtensionPropertyInfo> configureAction = null) |
||||
|
{ |
||||
|
return AddOrUpdateProperty( |
||||
|
typeof(TProperty), |
||||
|
propertyName, |
||||
|
configureAction |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public virtual ObjectExtensionInfo AddOrUpdateProperty( |
||||
|
[NotNull] Type propertyType, |
||||
|
[NotNull] string propertyName, |
||||
|
[CanBeNull] Action<ObjectExtensionPropertyInfo> configureAction = null) |
||||
|
{ |
||||
|
Check.NotNull(propertyType, nameof(propertyType)); |
||||
|
Check.NotNull(propertyName, nameof(propertyName)); |
||||
|
|
||||
|
var propertyInfo = Properties.GetOrAdd( |
||||
|
propertyName, |
||||
|
() => new ObjectExtensionPropertyInfo(this, propertyType, propertyName) |
||||
|
); |
||||
|
|
||||
|
configureAction?.Invoke(propertyInfo); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public virtual ImmutableList<ObjectExtensionPropertyInfo> GetProperties() |
||||
|
{ |
||||
|
return Properties.Values.ToImmutableList(); |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public virtual ObjectExtensionPropertyInfo GetPropertyOrNull( |
||||
|
[NotNull] string propertyName) |
||||
|
{ |
||||
|
Check.NotNullOrEmpty(propertyName, nameof(propertyName)); |
||||
|
|
||||
|
return Properties.GetOrDefault(propertyName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public class ObjectExtensionManager |
||||
|
{ |
||||
|
public static ObjectExtensionManager Instance { get; set; } = new ObjectExtensionManager(); |
||||
|
|
||||
|
protected Dictionary<Type, ObjectExtensionInfo> ObjectsExtensions { get; } |
||||
|
|
||||
|
protected internal ObjectExtensionManager() |
||||
|
{ |
||||
|
ObjectsExtensions = new Dictionary<Type, ObjectExtensionInfo>(); |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public virtual ObjectExtensionManager AddOrUpdate<TObject>( |
||||
|
[CanBeNull] Action<ObjectExtensionInfo> configureAction = null) |
||||
|
where TObject : IHasExtraProperties |
||||
|
{ |
||||
|
return AddOrUpdate(typeof(TObject), configureAction); |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public virtual ObjectExtensionManager AddOrUpdate( |
||||
|
[NotNull] Type type, |
||||
|
[CanBeNull] Action<ObjectExtensionInfo> configureAction = null) |
||||
|
{ |
||||
|
Check.AssignableTo<IHasExtraProperties>(type, nameof(type)); |
||||
|
|
||||
|
var extensionInfo = ObjectsExtensions.GetOrAdd( |
||||
|
type, |
||||
|
() => new ObjectExtensionInfo(type) |
||||
|
); |
||||
|
|
||||
|
configureAction?.Invoke(extensionInfo); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public virtual ObjectExtensionInfo GetOrNull<TObject>() |
||||
|
where TObject : IHasExtraProperties |
||||
|
{ |
||||
|
return GetOrNull(typeof(TObject)); |
||||
|
} |
||||
|
|
||||
|
[CanBeNull] |
||||
|
public virtual ObjectExtensionInfo GetOrNull([NotNull] Type type) |
||||
|
{ |
||||
|
Check.AssignableTo<IHasExtraProperties>(type, nameof(type)); |
||||
|
|
||||
|
return ObjectsExtensions.GetOrDefault(type); |
||||
|
} |
||||
|
|
||||
|
[NotNull] |
||||
|
public virtual ImmutableList<ObjectExtensionInfo> GetExtendedObjects() |
||||
|
{ |
||||
|
return ObjectsExtensions.Values.ToImmutableList(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using System; |
||||
|
using JetBrains.Annotations; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public static class ObjectExtensionManagerExtensions |
||||
|
{ |
||||
|
public static ObjectExtensionManager AddOrUpdateProperty<TObject, TProperty>( |
||||
|
[NotNull] this ObjectExtensionManager objectExtensionManager, |
||||
|
[NotNull] string propertyName, |
||||
|
[CanBeNull] Action<ObjectExtensionPropertyInfo> configureAction = null) |
||||
|
where TObject : IHasExtraProperties |
||||
|
{ |
||||
|
return objectExtensionManager.AddOrUpdateProperty( |
||||
|
typeof(TObject), |
||||
|
typeof(TProperty), |
||||
|
propertyName, |
||||
|
configureAction |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public static ObjectExtensionManager AddOrUpdateProperty( |
||||
|
[NotNull] this ObjectExtensionManager objectExtensionManager, |
||||
|
[NotNull] Type objectType, |
||||
|
[NotNull] Type propertyType, |
||||
|
[NotNull] string propertyName, |
||||
|
[CanBeNull] Action<ObjectExtensionPropertyInfo> configureAction = null) |
||||
|
{ |
||||
|
Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); |
||||
|
|
||||
|
return objectExtensionManager.AddOrUpdate( |
||||
|
objectType, |
||||
|
options => |
||||
|
{ |
||||
|
options.AddOrUpdateProperty( |
||||
|
propertyType, |
||||
|
propertyName, |
||||
|
configureAction |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public class ObjectExtensionPropertyInfo |
||||
|
{ |
||||
|
[NotNull] |
||||
|
public ObjectExtensionInfo ObjectExtension { get; } |
||||
|
|
||||
|
[NotNull] |
||||
|
public string Name { get; } |
||||
|
|
||||
|
[NotNull] |
||||
|
public Type Type { get; } |
||||
|
|
||||
|
[NotNull] |
||||
|
public Dictionary<object, object> Configuration { get; } |
||||
|
|
||||
|
public ObjectExtensionPropertyInfo( |
||||
|
[NotNull] ObjectExtensionInfo objectExtension, |
||||
|
[NotNull] Type type, |
||||
|
[NotNull] string name) |
||||
|
{ |
||||
|
ObjectExtension = Check.NotNull(objectExtension, nameof(objectExtension)); |
||||
|
Type = Check.NotNull(type, nameof(type)); |
||||
|
Name = Check.NotNull(name, nameof(name)); |
||||
|
|
||||
|
Configuration = new Dictionary<object, object>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.ObjectExtending.TestObjects; |
||||
|
using Volo.Abp.Testing; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace AutoMapper |
||||
|
{ |
||||
|
public class AbpAutoMapperExtensibleDtoExtensions_Tests : AbpIntegratedTest<AutoMapperTestModule> |
||||
|
{ |
||||
|
private readonly Volo.Abp.ObjectMapping.IObjectMapper _objectMapper; |
||||
|
|
||||
|
public AbpAutoMapperExtensibleDtoExtensions_Tests() |
||||
|
{ |
||||
|
_objectMapper = ServiceProvider.GetRequiredService<Volo.Abp.ObjectMapping.IObjectMapper>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MapExtraPropertiesTo_Should_Only_Map_Defined_Properties_By_Default() |
||||
|
{ |
||||
|
var person = new ExtensibleTestPerson() |
||||
|
.SetProperty("Name", "John") |
||||
|
.SetProperty("Age", 42) |
||||
|
.SetProperty("ChildCount", 2) |
||||
|
.SetProperty("Sex", "male"); |
||||
|
|
||||
|
var personDto = new ExtensibleTestPersonDto() |
||||
|
.SetProperty("ExistingDtoProperty", "existing-value"); |
||||
|
|
||||
|
_objectMapper.Map(person, personDto); |
||||
|
|
||||
|
personDto.GetProperty<string>("Name").ShouldBe("John"); //Defined in both classes
|
||||
|
personDto.HasProperty("Age").ShouldBeFalse(); //Not defined on the destination
|
||||
|
personDto.HasProperty("ChildCount").ShouldBeFalse(); //Not defined in the source
|
||||
|
personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
|
||||
|
personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.test.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj" /> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Testing; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public abstract class AbpObjectExtendingTestBase : AbpIntegratedTest<AbpObjectExtendingTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.ObjectExtending.TestObjects; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpObjectExtendingModule), |
||||
|
typeof(AbpTestBaseModule) |
||||
|
)] |
||||
|
public class AbpObjectExtendingTestModule : AbpModule |
||||
|
{ |
||||
|
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); |
||||
|
|
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
OneTimeRunner.Run(() => |
||||
|
{ |
||||
|
ObjectExtensionManager.Instance |
||||
|
.AddOrUpdateProperty<ExtensibleTestPerson, string>("Name") |
||||
|
.AddOrUpdateProperty<ExtensibleTestPerson, int>("Age") |
||||
|
.AddOrUpdateProperty<ExtensibleTestPersonDto, string>("Name") |
||||
|
.AddOrUpdateProperty<ExtensibleTestPersonDto, int>("ChildCount"); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
using Shouldly; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.ObjectExtending.TestObjects; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public class HasExtraPropertiesObjectExtendingExtensions_Tests : AbpObjectExtendingTestBase |
||||
|
{ |
||||
|
private readonly ExtensibleTestPerson _person; |
||||
|
private readonly ExtensibleTestPersonDto _personDto; |
||||
|
|
||||
|
public HasExtraPropertiesObjectExtendingExtensions_Tests() |
||||
|
{ |
||||
|
_person = new ExtensibleTestPerson() |
||||
|
.SetProperty("Name", "John") |
||||
|
.SetProperty("Age", 42) |
||||
|
.SetProperty("ChildCount", 2) |
||||
|
.SetProperty("Sex", "male"); |
||||
|
|
||||
|
_personDto = new ExtensibleTestPersonDto() |
||||
|
.SetProperty("ExistingDtoProperty", "existing-value"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MapExtraPropertiesTo_Should_Only_Map_Defined_Properties_By_Default() |
||||
|
{ |
||||
|
_person.MapExtraPropertiesTo(_personDto); |
||||
|
|
||||
|
_personDto.GetProperty<string>("Name").ShouldBe("John"); //Defined in both classes
|
||||
|
_personDto.HasProperty("Age").ShouldBeFalse(); //Not defined on the destination
|
||||
|
_personDto.HasProperty("ChildCount").ShouldBeFalse(); //Not defined in the source
|
||||
|
_personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
|
||||
|
_personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
|
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MapExtraPropertiesTo_Should_Only_Map_Source_Defined_Properties_If_Requested() |
||||
|
{ |
||||
|
_person.MapExtraPropertiesTo(_personDto, MappingPropertyDefinitionChecks.Source); |
||||
|
|
||||
|
_personDto.GetProperty<string>("Name").ShouldBe("John"); //Defined in both classes
|
||||
|
_personDto.GetProperty<int>("Age").ShouldBe(42); //Defined in source
|
||||
|
_personDto.HasProperty("ChildCount").ShouldBeFalse(); //Not defined in the source
|
||||
|
_personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
|
||||
|
_personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
|
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MapExtraPropertiesTo_Should_Only_Map_Destination_Defined_Properties_If_Requested() |
||||
|
{ |
||||
|
_person.MapExtraPropertiesTo(_personDto, MappingPropertyDefinitionChecks.Destination); |
||||
|
|
||||
|
_personDto.GetProperty<string>("Name").ShouldBe("John"); //Defined in both classes
|
||||
|
_personDto.GetProperty<int>("ChildCount").ShouldBe(2); //Defined in destination
|
||||
|
_personDto.HasProperty("Age").ShouldBeFalse(); //Not defined in destination
|
||||
|
_personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
|
||||
|
_personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
|
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MapExtraPropertiesTo_Should_Copy_all_With_No_Property_Definition_Check() |
||||
|
{ |
||||
|
_person.MapExtraPropertiesTo(_personDto, MappingPropertyDefinitionChecks.None); |
||||
|
|
||||
|
_personDto.GetProperty<string>("Name").ShouldBe("John"); |
||||
|
_personDto.GetProperty<int>("Age").ShouldBe(42); |
||||
|
_personDto.GetProperty<int>("ChildCount").ShouldBe(2); |
||||
|
_personDto.GetProperty<string>("Sex").ShouldBe("male"); |
||||
|
_personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
using System.Linq; |
||||
|
using Shouldly; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.ObjectExtending |
||||
|
{ |
||||
|
public class ObjectExtensionManager_Tests |
||||
|
{ |
||||
|
private readonly ObjectExtensionManager _objectExtensionManager; |
||||
|
|
||||
|
public ObjectExtensionManager_Tests() |
||||
|
{ |
||||
|
_objectExtensionManager = new ObjectExtensionManager(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Not_Add_Same_Property_Multiple_Times() |
||||
|
{ |
||||
|
_objectExtensionManager |
||||
|
.AddOrUpdateProperty<MyExtensibleObject, string>("TestProp") |
||||
|
.AddOrUpdateProperty<MyExtensibleObject, string>("TestProp"); |
||||
|
|
||||
|
var objectExtension = _objectExtensionManager.GetOrNull<MyExtensibleObject>(); |
||||
|
objectExtension.ShouldNotBeNull(); |
||||
|
|
||||
|
var properties = objectExtension.GetProperties(); |
||||
|
properties.Count.ShouldBe(1); |
||||
|
properties.FirstOrDefault(p => p.Name == "TestProp").ShouldNotBeNull(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Update_Property_Configuration() |
||||
|
{ |
||||
|
_objectExtensionManager |
||||
|
.AddOrUpdateProperty<MyExtensibleObject, string>( |
||||
|
"TestProp", |
||||
|
options => |
||||
|
{ |
||||
|
options.Configuration["TestConfig1"] = "TestConfig1-Value"; |
||||
|
} |
||||
|
).AddOrUpdateProperty<MyExtensibleObject, string>( |
||||
|
"TestProp", |
||||
|
options => |
||||
|
{ |
||||
|
options.Configuration["TestConfig2"] = "TestConfig2-Value"; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
var objectExtension = _objectExtensionManager.GetOrNull<MyExtensibleObject>(); |
||||
|
objectExtension.ShouldNotBeNull(); |
||||
|
|
||||
|
var property = objectExtension.GetPropertyOrNull("TestProp"); |
||||
|
property.ShouldNotBeNull(); |
||||
|
property.Configuration["TestConfig1"].ShouldBe("TestConfig1-Value"); |
||||
|
property.Configuration["TestConfig2"].ShouldBe("TestConfig2-Value"); |
||||
|
} |
||||
|
|
||||
|
private class MyExtensibleObject : ExtensibleObject |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.ObjectExtending.TestObjects |
||||
|
{ |
||||
|
public class ExtensibleTestPerson : ExtensibleObject |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.ObjectExtending.TestObjects |
||||
|
{ |
||||
|
public class ExtensibleTestPersonDto : ExtensibleObject |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.ObjectExtending; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName |
||||
|
{ |
||||
|
public static class MyProjectNameDtoExtensions |
||||
|
{ |
||||
|
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); |
||||
|
|
||||
|
public static void Configure() |
||||
|
{ |
||||
|
OneTimeRunner.Run(() => |
||||
|
{ |
||||
|
/* You can add extension properties to DTOs |
||||
|
* defined in the depended modules. |
||||
|
* |
||||
|
* Example: |
||||
|
* |
||||
|
* ObjectExtensionManager.Instance |
||||
|
* .AddOrUpdateProperty<IdentityRoleDto, string>("Title"); |
||||
|
* |
||||
|
* See the documentation for more: |
||||
|
* https://docs.abp.io/en/abp/latest/Object-Extensions
|
||||
|
*/ |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.ObjectExtending; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace MyCompanyName.MyProjectName.MongoDB |
||||
|
{ |
||||
|
public static class MyProjectNameEntityExtensions |
||||
|
{ |
||||
|
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); |
||||
|
|
||||
|
public static void Configure() |
||||
|
{ |
||||
|
OneTimeRunner.Run(() => |
||||
|
{ |
||||
|
/* You can extension properties to entities |
||||
|
* defined in the depended modules. |
||||
|
* |
||||
|
* Example: |
||||
|
* |
||||
|
* ObjectExtensionManager.Instance |
||||
|
* .AddOrUpdateProperty<IdentityRole, string>("Title"); |
||||
|
* |
||||
|
* See the documentation for more: |
||||
|
* https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities
|
||||
|
*/ |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue