mirror of https://github.com/abpframework/abp.git
131 changed files with 6051 additions and 1984 deletions
@ -0,0 +1,6 @@ |
|||
# Getting Started with the Startup Templates |
|||
|
|||
See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates: |
|||
|
|||
* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started-AspNetCore-MVC-Template.md) |
|||
* [Getting Started with the Angular UI](Getting-Started-Angular-Template.md) |
|||
@ -0,0 +1,3 @@ |
|||
# Object Extensions |
|||
|
|||
TODO |
|||
@ -0,0 +1,67 @@ |
|||
## Service Proxies |
|||
|
|||
It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on the server side) and **model objects** (matches to [DTOs](../../Data-Transfer-Objects) in the server side). |
|||
|
|||
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced: |
|||
|
|||
* It generates a **big, single** .ts file which has some problems; |
|||
* It get **too large** when your application grows. |
|||
* It doesn't fit into the **[modular](../../Module-Development-Basics) approach** of the ABP framework. |
|||
* It creates a bit **ugly code**. We want to have a clean code (just like if we write manually). |
|||
* It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies. |
|||
|
|||
ABP CLI `generate-proxies` command automatically generates the typescript client proxies by creating folders which separated by module names in the `src/app` folder. |
|||
Run the following command in the **root folder** of the angular application: |
|||
|
|||
```bash |
|||
abp generate-proxy |
|||
``` |
|||
|
|||
It only creates proxies only for your own application's services. It doesn't create proxies for the services of the application modules you're using (by default). There are several options. See the [CLI documentation](../../CLI). |
|||
|
|||
The files generated with the `--module all` option like below: |
|||
|
|||
 |
|||
|
|||
### Services |
|||
|
|||
Each generated service matches a back-end controller. The services methods call back-end APIs via [RestService](./Http-Requests#restservice). |
|||
|
|||
A variable named `apiName` (available as of v2.4) is defined in each service. `apiName` matches the module's RemoteServiceName. This variable passes to the `RestService` as a parameter at each request. If there is no microservice API defined in the environment, `RestService` uses the default. See [getting a specific API endpoint from application config](./Http-Requests#how-to-get-a-specific-api-endpoint-from-application-config) |
|||
|
|||
The `providedIn` property of the services is defined as `'root'`. Therefore no need to add a service as a provider to a module. You can use a service by injecting it into a constructor as shown below: |
|||
|
|||
```js |
|||
import { AbpApplicationConfigurationService } from '../app/shared/services'; |
|||
|
|||
//... |
|||
export class HomeComponent{ |
|||
constructor(private appConfigService: AbpApplicationConfigurationService) {} |
|||
|
|||
ngOnInit() { |
|||
this.appConfigService.get().subscribe() |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The Angular compiler removes the services that have not been injected anywhere from the final output. See the [tree-shakable providers documentation](https://angular.io/guide/dependency-injection-providers#tree-shakable-providers). |
|||
|
|||
### Models |
|||
|
|||
The generated models match the DTOs in the back-end. Each model is generated as a class under the `src/app/*/shared/models` folder. |
|||
|
|||
There are a few [base classes](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts) in the `@abp/ng.core` package. Some models extend these classes. |
|||
|
|||
A class instance can be created as shown below: |
|||
|
|||
```js |
|||
import { IdentityRoleCreateDto } from '../identity/shared/models'; |
|||
//... |
|||
const instance = new IdentityRoleCreateDto({name: 'Role 1', isDefault: false, isPublic: true}) |
|||
``` |
|||
|
|||
Initial values can optionally be passed to each class constructor. |
|||
|
|||
## What's Next? |
|||
|
|||
* [HTTP Requests](./Http-Requests) |
|||
|
After Width: | Height: | Size: 235 KiB |
@ -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 |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace VoloDocs.Web.Controllers |
|||
{ |
|||
public class HomeController : AbpController |
|||
{ |
|||
public void Index() |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -1,29 +1,59 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Docs; |
|||
using Volo.Docs.Projects; |
|||
|
|||
namespace VoloDocs.Web.Pages |
|||
{ |
|||
public class IndexModel : PageModel |
|||
{ |
|||
public IReadOnlyList<ProjectDto> Projects { get; set; } |
|||
|
|||
private readonly DocsUiOptions _urlUiOptions; |
|||
|
|||
public IndexModel(IOptions<DocsUiOptions> urlOptions) |
|||
private readonly IProjectAppService _projectAppService; |
|||
|
|||
public IndexModel(IOptions<DocsUiOptions> urlOptions, IProjectAppService projectAppService) |
|||
{ |
|||
_projectAppService = projectAppService; |
|||
_urlUiOptions = urlOptions.Value; |
|||
} |
|||
|
|||
public IActionResult OnGet() |
|||
public async Task<IActionResult> OnGetAsync() |
|||
{ |
|||
//TODO: Create HomeController & Index instead of Page. Otherwise, we have an empty Index.cshtml file.
|
|||
if (!_urlUiOptions.RoutePrefix.IsNullOrWhiteSpace()) |
|||
var projects = await _projectAppService.GetListAsync(); |
|||
|
|||
if (projects.Items.Count == 1) |
|||
{ |
|||
return Redirect("." + _urlUiOptions.RoutePrefix); |
|||
return await RedirectToProjectAsync(projects.Items.First()); |
|||
} |
|||
else if (projects.Items.Count > 1) |
|||
{ |
|||
Projects = projects.Items; |
|||
} |
|||
|
|||
return Page(); |
|||
} |
|||
|
|||
private async Task<IActionResult> RedirectToProjectAsync(ProjectDto project, string language = "en", string version = null) |
|||
{ |
|||
var path = GetUrlForProject(project, language, version); |
|||
return await Task.FromResult(Redirect(path)); |
|||
} |
|||
|
|||
//Eg: "/en/abp/latest"
|
|||
public string GetUrlForProject(ProjectDto project, string language = "en", string version = null) |
|||
{ |
|||
return "." + |
|||
_urlUiOptions.RoutePrefix.EnsureStartsWith('/').EnsureEndsWith('/') + |
|||
language.EnsureEndsWith('/') + |
|||
project.ShortName.EnsureEndsWith('/') + |
|||
(version ?? DocsAppConsts.Latest); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
File diff suppressed because it is too large
@ -0,0 +1,7 @@ |
|||
namespace Volo.Docs |
|||
{ |
|||
public class DocsDomainConsts |
|||
{ |
|||
public static string LanguageConfigFileName = "docs-langs.json"; |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using Newtonsoft.Json; |
|||
|
|||
namespace Volo.Extensions |
|||
{ |
|||
public static class JsonConvertExtensions |
|||
{ |
|||
public static bool TryDeserializeObject<T>(string jsonContent, out T result) |
|||
{ |
|||
try |
|||
{ |
|||
result = JsonConvert.DeserializeObject<T>(jsonContent); |
|||
return true; |
|||
} |
|||
catch |
|||
{ |
|||
result = default; |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue