Browse Source

feat: add Volo.Abp.LuckyPenny.AutoMapper package for commercial AutoMapper integration

Closes #25137
pull/25153/head
maliming 5 days ago
parent
commit
eb42ca88ef
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 151
      docs/en/framework/infrastructure/luckypenny-automapper.md
  2. 2
      docs/en/framework/infrastructure/object-to-object-mapping.md
  3. 2
      docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md
  4. 2
      framework/Volo.Abp.slnx
  5. 61
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/AutoMapper/AbpAutoMapperExtensibleObjectExtensions.cs
  6. 3
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/FodyWeavers.xml
  7. 30
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/FodyWeavers.xsd
  8. 22
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Microsoft/Extensions/DependencyInjection/AbpAutoMapperServiceCollectionExtensions.cs
  9. 3
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Properties/AssemblyInfo.cs
  10. 3
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo.Abp.LuckyPenny.AutoMapper.abppkg
  11. 73
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo.Abp.LuckyPenny.AutoMapper.abppkg.analyze.json
  12. 29
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo.Abp.LuckyPenny.AutoMapper.csproj
  13. 19
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperConfigurationContext.cs
  14. 29
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperConventionalRegistrar.cs
  15. 81
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperOptions.cs
  16. 76
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AbpLuckyPennyAutoMapperModule.cs
  17. 30
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AutoMapperAutoObjectMappingProvider.cs
  18. 150
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AutoMapperExpressionExtensions.cs
  19. 11
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/IAbpAutoMapperConfigurationContext.cs
  20. 8
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/IMapperAccessor.cs
  21. 8
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/MapperAccessor.cs
  22. 22
      framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/ObjectMapping/AbpAutoMapperObjectMapperExtensions.cs
  23. 82
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/AutoMapper/AbpAutoMapperExtensibleDtoExtensions_Tests.cs
  24. 3
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo.Abp.LuckyPenny.AutoMapper.Tests.abppkg
  25. 21
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo.Abp.LuckyPenny.AutoMapper.Tests.csproj
  26. 55
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Basic_Tests.cs
  27. 102
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_MaxDepth_Tests.cs
  28. 191
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs
  29. 177
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperExpressionExtensions_Tests.cs
  30. 19
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs
  31. 70
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_ConfigurationValidation_Tests.cs
  32. 87
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_CustomServiceConstruction_Tests.cs
  33. 83
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_Dependency_Injection_Tests.cs
  34. 32
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/ObjectMapperExtensions_Tests.cs
  35. 10
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntity.cs
  36. 10
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntityDto.cs
  37. 10
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntityDto2.cs
  38. 43
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntityDtoWithMappingMethods.cs
  39. 39
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntityToMyEntityDto2Mapper.cs
  40. 8
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnum.cs
  41. 8
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnumDto.cs
  42. 23
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs
  43. 10
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyNotMappedDto.cs
  44. 34
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/AbpLuckyPennyMultiLingualObjectsTestModule.cs
  45. 156
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs
  46. 23
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs
  47. 19
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs
  48. 12
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs
  49. 8
      framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs
  50. 1
      nupkg/common.ps1

151
docs/en/framework/infrastructure/luckypenny-automapper.md

@ -0,0 +1,151 @@
```json
//[doc-seo]
{
"Description": "Learn how to use the Volo.Abp.LuckyPenny.AutoMapper package to integrate the commercial AutoMapper (LuckyPenny) with ABP Framework."
}
```
# LuckyPenny AutoMapper Integration
## Introduction
[AutoMapper](https://automapper.org/) became a commercial product starting from version 15.x. The free open-source version (14.x) contains a [security vulnerability (GHSA-rvv3-g6hj-g44x)](https://github.com/advisories/GHSA-rvv3-g6hj-g44x) — a DoS (Denial of Service) vulnerability — and no patch will be released for the 14.x series. The patched version is only available in the commercial editions (15.x and later).
The existing [Volo.Abp.AutoMapper](https://www.nuget.org/packages/Volo.Abp.AutoMapper) package uses AutoMapper 14.x and remains available for existing users. If you hold a valid [LuckyPenny AutoMapper commercial license](https://automapper.io/), the `Volo.Abp.LuckyPenny.AutoMapper` package provides the same ABP AutoMapper integration built on the patched commercial version.
> If you don't need to use AutoMapper, you can migrate to [Mapperly](object-to-object-mapping.md#mapperly-integration), which is free and open-source. See the [AutoMapper to Mapperly migration guide](../../release-info/migration-guides/AutoMapper-To-Mapperly.md).
## Installation
Install the `Volo.Abp.LuckyPenny.AutoMapper` NuGet package to your project:
````bash
dotnet add package Volo.Abp.LuckyPenny.AutoMapper
````
Then add `AbpLuckyPennyAutoMapperModule` to your module's `[DependsOn]` attribute, replacing the existing `AbpAutoMapperModule`:
````csharp
[DependsOn(typeof(AbpLuckyPennyAutoMapperModule))]
public class MyModule : AbpModule
{
// ...
}
````
> **Note:** `Volo.Abp.LuckyPenny.AutoMapper` and `Volo.Abp.AutoMapper` should **not** be used together in the same application. They are mutually exclusive — choose one or the other.
## Usage
`Volo.Abp.LuckyPenny.AutoMapper` is a drop-in replacement for `Volo.Abp.AutoMapper`. All the same APIs, options, and extension methods are available. Refer to the [AutoMapper Integration](object-to-object-mapping.md#automapper-integration) section of the Object to Object Mapping documentation for full usage details.
The only difference from a user perspective is the module class name:
| | Package | Module class |
|---|---|---|
| Free (14.x, has security vulnerability) | `Volo.Abp.AutoMapper` | `AbpAutoMapperModule` |
| Commercial (patched) | `Volo.Abp.LuckyPenny.AutoMapper` | `AbpLuckyPennyAutoMapperModule` |
## License Configuration
The commercial AutoMapper uses an honor-system license. Without a configured key, everything works normally but a warning is written to the logs under the `LuckyPennySoftware.AutoMapper.License` category. To configure your license key, use `AbpAutoMapperOptions.Configurators`:
````csharp
[DependsOn(typeof(AbpLuckyPennyAutoMapperModule))]
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
options.Configurators.Add(ctx =>
{
ctx.MapperConfiguration.LicenseKey = "YOUR_LICENSE_KEY";
});
});
}
}
````
It is recommended to read the key from configuration rather than hardcoding it:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var licenseKey = context.Configuration["AutoMapper:LicenseKey"];
Configure<AbpAutoMapperOptions>(options =>
{
options.Configurators.Add(ctx =>
{
ctx.MapperConfiguration.LicenseKey = licenseKey;
});
});
}
````
````json
{
"AutoMapper": {
"LicenseKey": "YOUR_LICENSE_KEY"
}
}
````
To suppress the license warning in non-production environments (e.g. unit tests or local development), filter the log category in `Program.cs`:
````csharp
builder.Logging.AddFilter("LuckyPennySoftware.AutoMapper.License", LogLevel.None);
````
Or in `appsettings.Development.json`:
````json
{
"Logging": {
"LogLevel": {
"LuckyPennySoftware.AutoMapper.License": "None"
}
}
}
````
> **Client-side applications** (Blazor WebAssembly, MAUI, WPF, etc.) should **not** set the license key to avoid exposing it on the client. Use the log filter above to silence the warning instead.
## Obtaining a License
AutoMapper offers a **free Community License** and several paid plans.
### Community License (Free)
A free license is available to organizations that meet **all** of the following criteria:
- Annual gross revenue under **$5,000,000 USD**
- Never received more than **$10,000,000 USD** in outside capital (private equity or venture capital)
- Registered non-profits with an annual budget under **$5,000,000 USD** also qualify
> Government and quasi-government agencies do **not** qualify for the Community License.
Register for the Community License at: [https://luckypennysoftware.com/community](https://luckypennysoftware.com/community)
### Paid Plans
For organizations that do not meet the Community License criteria, paid plans are available at [https://luckypennysoftware.com/purchase](https://luckypennysoftware.com/purchase). For questions, contact [sales@luckypennysoftware.com](mailto:sales@luckypennysoftware.com).
## Migration from Volo.Abp.AutoMapper
To migrate an existing project from `Volo.Abp.AutoMapper` to `Volo.Abp.LuckyPenny.AutoMapper`:
1. Replace the NuGet package reference in all `*.csproj` files:
````diff
-<PackageReference Include="Volo.Abp.AutoMapper" />
+<PackageReference Include="Volo.Abp.LuckyPenny.AutoMapper" />
````
2. Replace the module dependency in all `*.cs` files:
````diff
-[DependsOn(typeof(AbpAutoMapperModule))]
+[DependsOn(typeof(AbpLuckyPennyAutoMapperModule))]
````
3. No other code changes are required. All types (`AbpAutoMapperOptions`, `IMapperAccessor`, `AutoMapperExpressionExtensions`, etc.) remain in the same namespaces.

2
docs/en/framework/infrastructure/object-to-object-mapping.md

@ -224,6 +224,8 @@ public class MyProfile : Profile
}
````
> AutoMapper 14.x contains a [known vulnerability (GHSA-rvv3-g6hj-g44x)](https://github.com/advisories/GHSA-rvv3-g6hj-g44x). ABP Framework has applied a code-level mitigation (`MaxDepth = 64`) to address this. If you hold a commercial AutoMapper license, you can use [Volo.Abp.LuckyPenny.AutoMapper](luckypenny-automapper.md) to upgrade to the officially patched version. Alternatively, you can migrate to [Mapperly](../../../release-info/migration-guides/AutoMapper-To-Mapperly.md).
## Mapperly Integration
[Mapperly](https://github.com/riok/mapperly) is a .NET source generator for generating object mappings. [Volo.Abp.Mapperly](https://www.nuget.org/packages/Volo.Abp.Mapperly) package defines the Mapperly integration for the `IObjectMapper`.

2
docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md

@ -6,6 +6,8 @@ The AutoMapper library is **no longer free for commercial use**. For more detail
ABP Framework provides both AutoMapper and Mapperly integrations. If your project currently uses AutoMapper and you don't have a commercial license, you can switch to Mapperly by following the steps outlined below.
> **Already have a commercial AutoMapper license?** Use the [Volo.Abp.LuckyPenny.AutoMapper](../../framework/infrastructure/luckypenny-automapper.md) package instead. It is a drop-in replacement for `Volo.Abp.AutoMapper` built on the patched commercial version of AutoMapper, requiring only two changes to your project.
## Migration Steps
Please open your project in an IDE(`Visual Studio`, `VS Code` or `JetBrains Rider`), then perform the following global search and replace operations:

2
framework/Volo.Abp.slnx

@ -48,6 +48,7 @@
<Project Path="src/Volo.Abp.Autofac.WebAssembly/Volo.Abp.Autofac.WebAssembly.csproj" />
<Project Path="src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj" />
<Project Path="src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj" />
<Project Path="src/Volo.Abp.LuckyPenny.AutoMapper/Volo.Abp.LuckyPenny.AutoMapper.csproj" />
<Project Path="src/Volo.Abp.AzureServiceBus/Volo.Abp.AzureServiceBus.csproj" />
<Project Path="src/Volo.Abp.BackgroundJobs.Abstractions/Volo.Abp.BackgroundJobs.Abstractions.csproj" />
<Project Path="src/Volo.Abp.BackgroundJobs.HangFire/Volo.Abp.BackgroundJobs.HangFire.csproj" />
@ -189,6 +190,7 @@
<Project Path="test/Volo.Abp.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj" />
<Project Path="test/Volo.Abp.Autofac.Tests/Volo.Abp.Autofac.Tests.csproj" />
<Project Path="test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj" />
<Project Path="test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo.Abp.LuckyPenny.AutoMapper.Tests.csproj" />
<Project Path="test/Volo.Abp.BackgroundJobs.Tests/Volo.Abp.BackgroundJobs.Tests.csproj" />
<Project Path="test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj" />
<Project Path="test/Volo.Abp.BlobStoring.Aws.Tests/Volo.Abp.BlobStoring.Aws.Tests.csproj" />

61
framework/src/Volo.Abp.LuckyPenny.AutoMapper/AutoMapper/AbpAutoMapperExtensibleObjectExtensions.cs

@ -0,0 +1,61 @@
using System.Collections.Generic;
using Volo.Abp.AutoMapper;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace AutoMapper;
public static class AbpAutoMapperExtensibleObjectExtensions
{
public static IMappingExpression<TSource, TDestination> MapExtraProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression,
MappingPropertyDefinitionChecks? definitionChecks = null,
string[]? ignoredProperties = null,
bool mapToRegularProperties = false)
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);
if (source.ExtraProperties == null || destination.ExtraProperties == null)
{
return result;
}
ExtensibleObjectMapper
.MapExtraPropertiesTo<TSource, TDestination>(
source.ExtraProperties,
result,
definitionChecks,
ignoredProperties
);
return result;
})
)
.ForSourceMember(x => x.ExtraProperties, x => x.DoNotValidate())
.AfterMap((source, destination, context) =>
{
if (mapToRegularProperties)
{
destination.SetExtraPropertiesToRegularProperties();
}
});
}
public static IMappingExpression<TSource, TDestination> IgnoreExtraProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IHasExtraProperties
where TSource : IHasExtraProperties
{
return mappingExpression.Ignore(x => x.ExtraProperties);
}
}

3
framework/src/Volo.Abp.LuckyPenny.AutoMapper/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.LuckyPenny.AutoMapper/FodyWeavers.xsd

@ -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>

22
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Microsoft/Extensions/DependencyInjection/AbpAutoMapperServiceCollectionExtensions.cs

@ -0,0 +1,22 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.AutoMapper;
using Volo.Abp.ObjectMapping;
namespace Microsoft.Extensions.DependencyInjection;
public static class AbpAutoMapperServiceCollectionExtensions
{
public static IServiceCollection AddAutoMapperObjectMapper(this IServiceCollection services)
{
return services.Replace(
ServiceDescriptor.Transient<IAutoObjectMappingProvider, AutoMapperAutoObjectMappingProvider>()
);
}
public static IServiceCollection AddAutoMapperObjectMapper<TContext>(this IServiceCollection services)
{
return services.Replace(
ServiceDescriptor.Transient<IAutoObjectMappingProvider<TContext>, AutoMapperAutoObjectMappingProvider<TContext>>()
);
}
}

3
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Properties/AssemblyInfo.cs

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Volo.Abp.LuckyPenny.AutoMapper.Tests")]

3
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo.Abp.LuckyPenny.AutoMapper.abppkg

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

73
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo.Abp.LuckyPenny.AutoMapper.abppkg.analyze.json

@ -0,0 +1,73 @@
{
"name": "Volo.Abp.LuckyPenny.AutoMapper",
"hash": "",
"contents": [
{
"namespace": "Volo.Abp.AutoMapper",
"dependsOnModules": [
{
"declaringAssemblyName": "Volo.Abp.ObjectMapping",
"namespace": "Volo.Abp.ObjectMapping",
"name": "AbpObjectMappingModule"
},
{
"declaringAssemblyName": "Volo.Abp.ObjectExtending",
"namespace": "Volo.Abp.ObjectExtending",
"name": "AbpObjectExtendingModule"
},
{
"declaringAssemblyName": "Volo.Abp.Auditing",
"namespace": "Volo.Abp.Auditing",
"name": "AbpAuditingModule"
}
],
"implementingInterfaces": [
{
"name": "IAbpModule",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IAbpModule"
},
{
"name": "IOnPreApplicationInitialization",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IOnPreApplicationInitialization"
},
{
"name": "IOnApplicationInitialization",
"namespace": "Volo.Abp",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.IOnApplicationInitialization"
},
{
"name": "IOnPostApplicationInitialization",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IOnPostApplicationInitialization"
},
{
"name": "IOnApplicationShutdown",
"namespace": "Volo.Abp",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.IOnApplicationShutdown"
},
{
"name": "IPreConfigureServices",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IPreConfigureServices"
},
{
"name": "IPostConfigureServices",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IPostConfigureServices"
}
],
"contentType": "abpModule",
"name": "AbpLuckyPennyAutoMapperModule",
"summary": null
}
]
}

29
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo.Abp.LuckyPenny.AutoMapper.csproj

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<AssemblyName>Volo.Abp.LuckyPenny.AutoMapper</AssemblyName>
<PackageId>Volo.Abp.LuckyPenny.AutoMapper</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.Auditing\Volo.Abp.Auditing.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" VersionOverride="16.1.1" />
</ItemGroup>
</Project>

19
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperConfigurationContext.cs

@ -0,0 +1,19 @@
using System;
using AutoMapper;
namespace Volo.Abp.AutoMapper;
public class AbpAutoMapperConfigurationContext : IAbpAutoMapperConfigurationContext
{
public IMapperConfigurationExpression MapperConfiguration { get; }
public IServiceProvider ServiceProvider { get; }
public AbpAutoMapperConfigurationContext(
IMapperConfigurationExpression mapperConfigurationExpression,
IServiceProvider serviceProvider)
{
MapperConfiguration = mapperConfigurationExpression;
ServiceProvider = serviceProvider;
}
}

29
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperConventionalRegistrar.cs

@ -0,0 +1,29 @@
using System;
using System.Linq;
using AutoMapper;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AutoMapper;
public class AbpAutoMapperConventionalRegistrar : DefaultConventionalRegistrar
{
protected readonly Type[] OpenTypes = {
typeof(IValueResolver<,,>),
typeof(IMemberValueResolver<,,,>),
typeof(ITypeConverter<,>),
typeof(IValueConverter<,>),
typeof(IMappingAction<,>)
};
protected override bool IsConventionalRegistrationDisabled(Type type)
{
return !type.GetInterfaces().Any(x => x.IsGenericType && OpenTypes.Contains(x.GetGenericTypeDefinition())) ||
base.IsConventionalRegistrationDisabled(type);
}
protected override ServiceLifetime? GetDefaultLifeTimeOrNull(Type type)
{
return ServiceLifetime.Transient;
}
}

81
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperOptions.cs

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using Volo.Abp.Collections;
namespace Volo.Abp.AutoMapper;
public class AbpAutoMapperOptions
{
public List<Action<IAbpAutoMapperConfigurationContext>> Configurators { get; }
public ITypeList<Profile> ValidatingProfiles { get; set; }
/// <summary>
/// Default MaxDepth applied to all maps that don't have an explicit MaxDepth configured.
/// Set to null to disable the default MaxDepth behavior.
/// Default: 64.
/// </summary>
public int? DefaultMaxDepth { get; set; } = 64;
public AbpAutoMapperOptions()
{
Configurators = new List<Action<IAbpAutoMapperConfigurationContext>>();
ValidatingProfiles = new TypeList<Profile>();
}
public void AddMaps<TModule>(bool validate = false)
{
var assembly = typeof(TModule).Assembly;
Configurators.Add(context =>
{
context.MapperConfiguration.AddMaps(assembly);
});
if (validate)
{
var profileTypes = assembly
.DefinedTypes
.Where(type => typeof(Profile).IsAssignableFrom(type) && !type.IsAbstract && !type.IsGenericType);
foreach (var profileType in profileTypes)
{
ValidatingProfiles.Add(profileType);
}
}
}
public void AddProfile<TProfile>(bool validate = false)
where TProfile : Profile, new()
{
Configurators.Add(context =>
{
context.MapperConfiguration.AddProfile<TProfile>();
});
if (validate)
{
ValidateProfile(typeof(TProfile));
}
}
public void ValidateProfile<TProfile>(bool validate = true)
where TProfile : Profile
{
ValidateProfile(typeof(TProfile), validate);
}
public void ValidateProfile(Type profileType, bool validate = true)
{
if (validate)
{
ValidatingProfiles.AddIfNotContains(profileType);
}
else
{
ValidatingProfiles.Remove(profileType);
}
}
}

76
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AbpLuckyPennyAutoMapperModule.cs

@ -0,0 +1,76 @@
using System;
using AutoMapper;
using AutoMapper.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.Auditing;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.AutoMapper;
[DependsOn(
typeof(AbpObjectMappingModule),
typeof(AbpObjectExtendingModule),
typeof(AbpAuditingModule)
)]
public class AbpLuckyPennyAutoMapperModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddConventionalRegistrar(new AbpAutoMapperConventionalRegistrar());
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper();
context.Services.AddSingleton<IConfigurationProvider>(sp =>
{
using (var scope = sp.CreateScope())
{
var options = scope.ServiceProvider.GetRequiredService<IOptions<AbpAutoMapperOptions>>().Value;
var mapperConfigurationExpression = sp.GetRequiredService<IOptions<MapperConfigurationExpression>>().Value;
var autoMapperConfigurationContext = new AbpAutoMapperConfigurationContext(mapperConfigurationExpression, scope.ServiceProvider);
foreach (var configurator in options.Configurators)
{
configurator(autoMapperConfigurationContext);
}
if (options.DefaultMaxDepth.HasValue)
{
mapperConfigurationExpression.Internal().ForAllMaps((typeMap, _) =>
{
if (typeMap.MaxDepth == 0)
{
typeMap.MaxDepth = options.DefaultMaxDepth.Value;
}
});
}
var loggerFactory = sp.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
var mapperConfiguration = new MapperConfiguration(mapperConfigurationExpression, loggerFactory);
foreach (var profileType in options.ValidatingProfiles)
{
mapperConfiguration.Internal().AssertConfigurationIsValid(((Profile)Activator.CreateInstance(profileType)!).ProfileName);
}
return mapperConfiguration;
}
});
context.Services.AddTransient<IMapper>(sp => sp.GetRequiredService<IConfigurationProvider>().CreateMapper(sp.GetService));
context.Services.AddTransient<MapperAccessor>(sp => new MapperAccessor()
{
Mapper = sp.GetRequiredService<IMapper>()
});
context.Services.AddTransient<IMapperAccessor>(provider => provider.GetRequiredService<MapperAccessor>());
}
}

30
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AutoMapperAutoObjectMappingProvider.cs

@ -0,0 +1,30 @@
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.AutoMapper;
public class AutoMapperAutoObjectMappingProvider<TContext> : AutoMapperAutoObjectMappingProvider, IAutoObjectMappingProvider<TContext>
{
public AutoMapperAutoObjectMappingProvider(IMapperAccessor mapperAccessor)
: base(mapperAccessor)
{
}
}
public class AutoMapperAutoObjectMappingProvider : IAutoObjectMappingProvider
{
public IMapperAccessor MapperAccessor { get; }
public AutoMapperAutoObjectMappingProvider(IMapperAccessor mapperAccessor)
{
MapperAccessor = mapperAccessor;
}
public virtual TDestination Map<TSource, TDestination>(object source)
{
return MapperAccessor.Mapper.Map<TDestination>(source);
}
public virtual TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return MapperAccessor.Mapper.Map(source, destination);
}
}

150
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/AutoMapperExpressionExtensions.cs

@ -0,0 +1,150 @@
using System;
using System.Linq.Expressions;
using AutoMapper;
using Volo.Abp.Auditing;
namespace Volo.Abp.AutoMapper;
public static class AutoMapperExpressionExtensions
{
public static IMappingExpression<TDestination, TMember> Ignore<TDestination, TMember, TResult>(this IMappingExpression<TDestination, TMember> mappingExpression, Expression<Func<TMember, TResult>> destinationMember)
{
return mappingExpression.ForMember(destinationMember, opts => opts.Ignore());
}
public static IMappingExpression<TSource, TDestination> IgnoreHasCreationTimeProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IHasCreationTime
{
return mappingExpression.Ignore(x => x.CreationTime);
}
public static IMappingExpression<TSource, TDestination> IgnoreMayHaveCreatorProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IMayHaveCreator
{
return mappingExpression.Ignore(x => x.CreatorId);
}
public static IMappingExpression<TSource, TDestination> IgnoreCreationAuditedObjectProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : ICreationAuditedObject
{
return mappingExpression
.IgnoreHasCreationTimeProperties()
.IgnoreMayHaveCreatorProperties();
}
public static IMappingExpression<TSource, TDestination> IgnoreHasModificationTimeProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IHasModificationTime
{
return mappingExpression.Ignore(x => x.LastModificationTime);
}
public static IMappingExpression<TSource, TDestination> IgnoreModificationAuditedObjectProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IModificationAuditedObject
{
return mappingExpression
.IgnoreHasModificationTimeProperties()
.Ignore(x => x.LastModifierId);
}
public static IMappingExpression<TSource, TDestination> IgnoreAuditedObjectProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IAuditedObject
{
return mappingExpression
.IgnoreCreationAuditedObjectProperties()
.IgnoreModificationAuditedObjectProperties();
}
public static IMappingExpression<TSource, TDestination> IgnoreSoftDeleteProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : ISoftDelete
{
return mappingExpression.Ignore(x => x.IsDeleted);
}
public static IMappingExpression<TSource, TDestination> IgnoreHasDeletionTimeProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IHasDeletionTime
{
return mappingExpression
.IgnoreSoftDeleteProperties()
.Ignore(x => x.DeletionTime);
}
public static IMappingExpression<TSource, TDestination> IgnoreDeletionAuditedObjectProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IDeletionAuditedObject
{
return mappingExpression
.IgnoreHasDeletionTimeProperties()
.Ignore(x => x.DeleterId);
}
public static IMappingExpression<TSource, TDestination> IgnoreFullAuditedObjectProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IFullAuditedObject
{
return mappingExpression
.IgnoreAuditedObjectProperties()
.IgnoreDeletionAuditedObjectProperties();
}
public static IMappingExpression<TSource, TDestination> IgnoreMayHaveCreatorProperties<TSource, TDestination, TUser>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IMayHaveCreator<TUser>
{
return mappingExpression
.Ignore(x => x.Creator);
}
public static IMappingExpression<TSource, TDestination> IgnoreCreationAuditedObjectProperties<TSource, TDestination, TUser>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : ICreationAuditedObject<TUser>
{
return mappingExpression
.IgnoreCreationAuditedObjectProperties()
.IgnoreMayHaveCreatorProperties<TSource, TDestination, TUser>();
}
public static IMappingExpression<TSource, TDestination> IgnoreModificationAuditedObjectProperties<TSource, TDestination, TUser>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IModificationAuditedObject<TUser>
{
return mappingExpression
.IgnoreModificationAuditedObjectProperties()
.Ignore(x => x.LastModifier);
}
public static IMappingExpression<TSource, TDestination> IgnoreAuditedObjectProperties<TSource, TDestination, TUser>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IAuditedObject<TUser>
{
return mappingExpression
.IgnoreCreationAuditedObjectProperties<TSource, TDestination, TUser>()
.IgnoreModificationAuditedObjectProperties<TSource, TDestination, TUser>();
}
public static IMappingExpression<TSource, TDestination> IgnoreDeletionAuditedObjectProperties<TSource, TDestination, TUser>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IDeletionAuditedObject<TUser>
{
return mappingExpression
.IgnoreDeletionAuditedObjectProperties()
.Ignore(x => x.Deleter);
}
public static IMappingExpression<TSource, TDestination> IgnoreFullAuditedObjectProperties<TSource, TDestination, TUser>(
this IMappingExpression<TSource, TDestination> mappingExpression)
where TDestination : IFullAuditedObject<TUser>
{
return mappingExpression
.IgnoreAuditedObjectProperties<TSource, TDestination, TUser>()
.IgnoreDeletionAuditedObjectProperties<TSource, TDestination, TUser>();
}
}

11
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/IAbpAutoMapperConfigurationContext.cs

@ -0,0 +1,11 @@
using System;
using AutoMapper;
namespace Volo.Abp.AutoMapper;
public interface IAbpAutoMapperConfigurationContext
{
IMapperConfigurationExpression MapperConfiguration { get; }
IServiceProvider ServiceProvider { get; }
}

8
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/IMapperAccessor.cs

@ -0,0 +1,8 @@
using AutoMapper;
namespace Volo.Abp.AutoMapper;
public interface IMapperAccessor
{
IMapper Mapper { get; }
}

8
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/AutoMapper/MapperAccessor.cs

@ -0,0 +1,8 @@
using AutoMapper;
namespace Volo.Abp.AutoMapper;
internal class MapperAccessor : IMapperAccessor
{
public IMapper Mapper { get; set; } = default!;
}

22
framework/src/Volo.Abp.LuckyPenny.AutoMapper/Volo/Abp/ObjectMapping/AbpAutoMapperObjectMapperExtensions.cs

@ -0,0 +1,22 @@
using AutoMapper;
using Volo.Abp.AutoMapper;
namespace Volo.Abp.ObjectMapping;
public static class AbpAutoMapperObjectMapperExtensions
{
public static IMapper GetMapper(this IObjectMapper objectMapper)
{
return objectMapper.AutoObjectMappingProvider.GetMapper();
}
public static IMapper GetMapper(this IAutoObjectMappingProvider autoObjectMappingProvider)
{
if (autoObjectMappingProvider is AutoMapperAutoObjectMappingProvider autoMapperAutoObjectMappingProvider)
{
return autoMapperAutoObjectMappingProvider.MapperAccessor.Mapper;
}
throw new AbpException($"Given object is not an instance of {typeof(AutoMapperAutoObjectMappingProvider).AssemblyQualifiedName}. The type of the given object it {autoObjectMappingProvider.GetType().AssemblyQualifiedName}");
}
}

82
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/AutoMapper/AbpAutoMapperExtensibleDtoExtensions_Tests.cs

@ -0,0 +1,82 @@
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")
.SetProperty("CityName", "Adana");
var personDto = new ExtensibleTestPersonDto()
.SetProperty("ExistingDtoProperty", "existing-value");
_objectMapper.Map(person, personDto);
personDto.GetProperty<string>("Name").ShouldBe("John"); //Defined in both classes
personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
personDto.GetProperty<int>("ChildCount").ShouldBe(0); //Not defined in the source, but was set to the default value by ExtensibleTestPersonDto constructor
personDto.GetProperty("CityName").ShouldBeNull(); //Ignored, but was set to the default value by ExtensibleTestPersonDto constructor
personDto.HasProperty("Age").ShouldBeFalse(); //Not defined on the destination
personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
}
[Fact]
public void MapExtraProperties_Also_Should_Map_To_RegularProperties()
{
var person = new ExtensibleTestPerson()
.SetProperty("Name", "John")
.SetProperty("Age", 42);
var personDto = new ExtensibleTestPersonWithRegularPropertiesDto()
.SetProperty("IsActive", true);
_objectMapper.Map(person, personDto);
//Defined in both classes
personDto.HasProperty("Name").ShouldBe(false);
personDto.Name.ShouldBe("John");
//Defined in both classes
personDto.HasProperty("Age").ShouldBe(false);
personDto.Age.ShouldBe(42);
//Should not clear existing values
personDto.HasProperty("IsActive").ShouldBe(false);
personDto.IsActive.ShouldBe(true);
}
[Fact]
public void MapExtraPropertiesTo_Should_Ignored_If_ExtraProperties_Is_Null()
{
var person = new ExtensibleTestPerson();
person.SetExtraPropertiesAsNull();
var personDto = new ExtensibleTestPersonDto();
personDto.SetExtraPropertiesAsNull();
Should.NotThrow(() => _objectMapper.Map(person, personDto));
person.ExtraProperties.ShouldBe(null);
personDto.ExtraProperties.ShouldBeEmpty();
}
}

3
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo.Abp.LuckyPenny.AutoMapper.Tests.abppkg

@ -0,0 +1,3 @@
{
"role": "test.framework"
}

21
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo.Abp.LuckyPenny.AutoMapper.Tests.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Volo.Abp.LuckyPenny.AutoMapper.Tests</AssemblyName>
<PackageId>Volo.Abp.LuckyPenny.AutoMapper.Tests</PackageId>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.LuckyPenny.AutoMapper\Volo.Abp.LuckyPenny.AutoMapper.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.MultiLingualObjects\Volo.Abp.MultiLingualObjects.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Settings\Volo.Abp.Settings.csproj" />
<ProjectReference Include="..\..\test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>
</Project>

55
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Basic_Tests.cs

@ -0,0 +1,55 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.AutoMapper.SampleClasses;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.AutoMapper;
public class AbpAutoMapperModule_Basic_Tests : AbpIntegratedTest<AutoMapperTestModule>
{
private readonly IObjectMapper _objectMapper;
public AbpAutoMapperModule_Basic_Tests()
{
_objectMapper = ServiceProvider.GetRequiredService<IObjectMapper>();
}
[Fact]
public void Should_Replace_IAutoObjectMappingProvider()
{
Assert.True(ServiceProvider.GetRequiredService<IAutoObjectMappingProvider>() is AutoMapperAutoObjectMappingProvider);
}
[Fact]
public void Should_Get_Internal_Mapper()
{
_objectMapper.GetMapper().ShouldNotBeNull();
_objectMapper.AutoObjectMappingProvider.GetMapper().ShouldNotBeNull();
}
[Fact]
public void Should_Map_Objects_With_AutoMap_Attributes()
{
var dto = _objectMapper.Map<MyEntity, MyEntityDto>(new MyEntity { Number = 42 });
dto.Number.ShouldBe(42);
}
[Fact]
public void Should_Map_Enum()
{
var dto = _objectMapper.Map<MyEnum, MyEnumDto>(MyEnum.Value3);
dto.ShouldBe(MyEnumDto.Value2); //Value2 is same as Value3
}
//[Fact] TODO: Disabled because of https://github.com/AutoMapper/AutoMapper/pull/2379#issuecomment-355899664
/*public void Should_Not_Map_Objects_With_AutoMap_Attributes()
{
Assert.ThrowsAny<Exception>(() =>
{
_objectMapper.Map<MyEntity, MyNotMappedDto>(new MyEntity {Number = 42});
});
}*/
}

102
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_MaxDepth_Tests.cs

@ -0,0 +1,102 @@
using AutoMapper;
using AutoMapper.Internal;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.AutoMapper.SampleClasses;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.AutoMapper;
public class AbpAutoMapperModule_MaxDepth_Tests : AbpIntegratedTest<AutoMapperTestModule>
{
private readonly IConfigurationProvider _configurationProvider;
public AbpAutoMapperModule_MaxDepth_Tests()
{
_configurationProvider = ServiceProvider.GetRequiredService<IConfigurationProvider>();
}
[Fact]
public void Should_Set_Default_MaxDepth_For_All_Maps()
{
var typeMap = _configurationProvider.Internal().FindTypeMapFor<MyEntity, MyEntityDto>();
typeMap.ShouldNotBeNull();
typeMap.MaxDepth.ShouldBe(64);
}
}
public class AbpAutoMapperModule_CustomMaxDepth_Tests : AbpIntegratedTest<AbpAutoMapperModule_CustomMaxDepth_Tests.TestModule>
{
private readonly IConfigurationProvider _configurationProvider;
public AbpAutoMapperModule_CustomMaxDepth_Tests()
{
_configurationProvider = ServiceProvider.GetRequiredService<IConfigurationProvider>();
}
[Fact]
public void Should_Not_Override_Custom_MaxDepth()
{
var typeMap = _configurationProvider.Internal().FindTypeMapFor<MyEntity, MyEntityDto>();
typeMap.ShouldNotBeNull();
typeMap.MaxDepth.ShouldBe(10);
}
[DependsOn(
typeof(AbpLuckyPennyAutoMapperModule),
typeof(AbpObjectExtendingTestModule)
)]
public class TestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
options.Configurators.Add(ctx =>
{
ctx.MapperConfiguration.CreateMap<MyEntity, MyEntityDto>().MaxDepth(10);
});
});
}
}
}
public class AbpAutoMapperModule_DisabledMaxDepth_Tests : AbpIntegratedTest<AbpAutoMapperModule_DisabledMaxDepth_Tests.TestModule>
{
private readonly IConfigurationProvider _configurationProvider;
public AbpAutoMapperModule_DisabledMaxDepth_Tests()
{
_configurationProvider = ServiceProvider.GetRequiredService<IConfigurationProvider>();
}
[Fact]
public void Should_Not_Set_MaxDepth_When_Disabled()
{
var typeMap = _configurationProvider.Internal().FindTypeMapFor<MyEntity, MyEntityDto>();
typeMap.ShouldNotBeNull();
typeMap.MaxDepth.ShouldBe(0);
}
[DependsOn(
typeof(AbpLuckyPennyAutoMapperModule),
typeof(AbpObjectExtendingTestModule)
)]
public class TestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
options.DefaultMaxDepth = null;
options.Configurators.Add(ctx =>
{
ctx.MapperConfiguration.CreateMap<MyEntity, MyEntityDto>();
});
});
}
}
}

191
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs

@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.AutoMapper.SampleClasses;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.AutoMapper;
public class AbpAutoMapperModule_Specific_ObjectMapper_Tests : AbpIntegratedTest<AutoMapperTestModule>
{
private readonly IObjectMapper _objectMapper;
public AbpAutoMapperModule_Specific_ObjectMapper_Tests()
{
_objectMapper = ServiceProvider.GetRequiredService<IObjectMapper>();
}
[Fact]
public void Should_Use_Specific_Object_Mapper_If_Registered()
{
var dto = _objectMapper.Map<MyEntity, MyEntityDto2>(new MyEntity { Number = 42 });
dto.Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
}
[Fact]
public void Specific_Object_Mapper_Should_Be_Used_For_Collections_If_Registered()
{
// IEnumerable
_objectMapper.Map<IEnumerable<MyEntity>, IEnumerable<MyEntityDto2>>(new List<MyEntity>()
{
new MyEntity { Number = 42 }
}).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
var destination = new List<MyEntityDto2>()
{
new MyEntityDto2 { Number = 44 }
};
var returnIEnumerable = _objectMapper.Map<IEnumerable<MyEntity>, IEnumerable<MyEntityDto2>>(
new List<MyEntity>()
{
new MyEntity { Number = 42 }
}, destination);
returnIEnumerable.First().Number.ShouldBe(43);
ReferenceEquals(destination, returnIEnumerable).ShouldBeTrue();
// ICollection
_objectMapper.Map<ICollection<MyEntity>, ICollection<MyEntityDto2>>(new List<MyEntity>()
{
new MyEntity { Number = 42 }
}).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
var returnICollection = _objectMapper.Map<ICollection<MyEntity>, ICollection<MyEntityDto2>>(
new List<MyEntity>()
{
new MyEntity { Number = 42 }
}, destination);
returnICollection.First().Number.ShouldBe(43);
ReferenceEquals(destination, returnICollection).ShouldBeTrue();
// Collection
_objectMapper.Map<Collection<MyEntity>, Collection<MyEntityDto2>>(new Collection<MyEntity>()
{
new MyEntity { Number = 42 }
}).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
var destination2 = new Collection<MyEntityDto2>()
{
new MyEntityDto2 { Number = 44 }
};
var returnCollection = _objectMapper.Map<Collection<MyEntity>, Collection<MyEntityDto2>>(
new Collection<MyEntity>()
{
new MyEntity { Number = 42 }
}, destination2);
returnCollection.First().Number.ShouldBe(43);
ReferenceEquals(destination2, returnCollection).ShouldBeTrue();
// IList
_objectMapper.Map<IList<MyEntity>, IList<MyEntityDto2>>(new List<MyEntity>()
{
new MyEntity { Number = 42 }
}).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
var returnIList = _objectMapper.Map<IList<MyEntity>, IList<MyEntityDto2>>(
new List<MyEntity>()
{
new MyEntity { Number = 42 }
}, destination);
returnIList.First().Number.ShouldBe(43);
ReferenceEquals(destination, returnIList).ShouldBeTrue();
// List
_objectMapper.Map<List<MyEntity>, List<MyEntityDto2>>(new List<MyEntity>()
{
new MyEntity { Number = 42 }
}).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
var returnList = _objectMapper.Map<List<MyEntity>, List<MyEntityDto2>>(
new List<MyEntity>()
{
new MyEntity { Number = 42 }
}, destination);
returnList.First().Number.ShouldBe(43);
ReferenceEquals(destination, returnList).ShouldBeTrue();
// Array
_objectMapper.Map<MyEntity[], MyEntityDto2[]>(new MyEntity[]
{
new MyEntity { Number = 42 }
}).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
var destinationArray = new MyEntityDto2[]
{
new MyEntityDto2 { Number = 40 }
};
var returnArray = _objectMapper.Map<MyEntity[], MyEntityDto2[]>(new MyEntity[]
{
new MyEntity { Number = 42 }
}, destinationArray);
returnArray.First().Number.ShouldBe(43);
// array should not be changed. Same as AutoMapper.
destinationArray.First().Number.ShouldBe(40);
ReferenceEquals(returnArray, destinationArray).ShouldBeFalse();
}
[Fact]
public void Specific_Object_Mapper_Should_Support_Multiple_IObjectMapper_Interfaces()
{
var myEntityDto2 = _objectMapper.Map<MyEntity, MyEntityDto2>(new MyEntity { Number = 42 });
myEntityDto2.Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
var myEntity = _objectMapper.Map<MyEntityDto2, MyEntity>(new MyEntityDto2 { Number = 42 });
myEntity.Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
// IEnumerable
_objectMapper.Map<IEnumerable<MyEntity>, IEnumerable<MyEntityDto2>>(new List<MyEntity>()
{
new MyEntity { Number = 42 }
}).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
_objectMapper.Map<IEnumerable<MyEntityDto2>, IEnumerable<MyEntity>>(new List<MyEntityDto2>()
{
new MyEntityDto2 { Number = 42 }
}).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source.
}
[Fact]
public void Should_Use_Destination_Object_Constructor_If_Available()
{
var id = Guid.NewGuid();
var dto = _objectMapper.Map<MyEntity, MyEntityDtoWithMappingMethods>(new MyEntity { Number = 42, Id = id });
dto.Key.ShouldBe(id);
dto.No.ShouldBe(42);
}
[Fact]
public void Should_Use_Destination_Object_MapFrom_Method_If_Available()
{
var id = Guid.NewGuid();
var dto = new MyEntityDtoWithMappingMethods();
_objectMapper.Map(new MyEntity { Number = 42, Id = id }, dto);
dto.Key.ShouldBe(id);
dto.No.ShouldBe(42);
}
[Fact]
public void Should_Use_Source_Object_Method_If_Available_To_Create_New_Object()
{
var id = Guid.NewGuid();
var entity = _objectMapper.Map<MyEntityDtoWithMappingMethods, MyEntity>(new MyEntityDtoWithMappingMethods { Key = id, No = 42 });
entity.Id.ShouldBe(id);
entity.Number.ShouldBe(42);
}
[Fact]
public void Should_Use_Source_Object_Method_If_Available_To_Map_Existing_Object()
{
var id = Guid.NewGuid();
var entity = new MyEntity();
_objectMapper.Map(new MyEntityDtoWithMappingMethods { Key = id, No = 42 }, entity);
entity.Id.ShouldBe(id);
entity.Number.ShouldBe(42);
}
}

177
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperExpressionExtensions_Tests.cs

@ -0,0 +1,177 @@
using System;
using AutoMapper;
using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;
using Volo.Abp.Auditing;
using Xunit;
namespace Volo.Abp.AutoMapper;
public class AutoMapperExpressionExtensions_Tests
{
[Fact]
public void Should_Ignore_Configured_Property()
{
var mapper = CreateMapper(
cfg => cfg
.CreateMap<SimpleClass1, SimpleClass2>()
.Ignore(x => x.Value2)
.Ignore(x => x.Value3)
);
var obj2 = mapper.Map<SimpleClass2>(
new SimpleClass1
{
Value1 = "v1",
Value2 = "v2"
}
);
obj2.Value1.ShouldBe("v1");
obj2.Value2.ShouldBeNull();
obj2.Value3.ShouldBeNull();
}
[Fact]
public void Should_Ignore_Audit_Properties()
{
var mapper = CreateMapper(
cfg => cfg
.CreateMap<SimpleClassAudited1, SimpleClassAudited2>()
.IgnoreFullAuditedObjectProperties()
);
var obj2 = mapper.Map<SimpleClassAudited2>(
new SimpleClassAudited1
{
CreationTime = DateTime.Now,
CreatorId = Guid.NewGuid(),
LastModificationTime = DateTime.Now,
LastModifierId = Guid.NewGuid(),
DeleterId = Guid.NewGuid(),
DeletionTime = DateTime.Now,
IsDeleted = true
}
);
obj2.CreationTime.ShouldBe(default);
obj2.CreatorId.ShouldBeNull();
obj2.LastModificationTime.ShouldBe(default);
obj2.LastModifierId.ShouldBeNull();
obj2.DeleterId.ShouldBeNull();
obj2.DeletionTime.ShouldBeNull();
obj2.IsDeleted.ShouldBeFalse();
}
[Fact]
public void Should_Ignore_Audit_Properties_With_User()
{
var mapper = CreateMapper(
cfg => cfg
.CreateMap<SimpleClassAuditedWithUser1, SimpleClassAuditedWithUser2>()
.IgnoreFullAuditedObjectProperties<SimpleClassAuditedWithUser1, SimpleClassAuditedWithUser2, SimpleUser>()
);
var obj2 = mapper.Map<SimpleClassAuditedWithUser2>(
new SimpleClassAuditedWithUser1
{
CreationTime = DateTime.Now,
CreatorId = Guid.NewGuid(),
LastModificationTime = DateTime.Now,
LastModifierId = Guid.NewGuid(),
DeleterId = Guid.NewGuid(),
DeletionTime = DateTime.Now,
IsDeleted = true,
Creator = new SimpleUser(),
Deleter = new SimpleUser(),
LastModifier = new SimpleUser()
}
);
obj2.CreationTime.ShouldBe(default);
obj2.CreatorId.ShouldBeNull();
obj2.LastModificationTime.ShouldBe(default);
obj2.LastModifierId.ShouldBeNull();
obj2.DeleterId.ShouldBeNull();
obj2.DeletionTime.ShouldBeNull();
obj2.IsDeleted.ShouldBeFalse();
obj2.Creator.ShouldBeNull();
obj2.Deleter.ShouldBeNull();
obj2.LastModifier.ShouldBeNull();
}
private static IMapper CreateMapper(Action<IMapperConfigurationExpression> configure)
{
var configuration = new MapperConfiguration(configure, NullLoggerFactory.Instance);
configuration.AssertConfigurationIsValid();
return configuration.CreateMapper();
}
public class SimpleClass1
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
public class SimpleClass2
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public string Value3 { get; set; }
}
public class SimpleClassAudited1 : IFullAuditedObject
{
public DateTime CreationTime { get; set; }
public Guid? CreatorId { get; set; }
public DateTime? LastModificationTime { get; set; }
public Guid? LastModifierId { get; set; }
public bool IsDeleted { get; set; }
public DateTime? DeletionTime { get; set; }
public Guid? DeleterId { get; set; }
}
public class SimpleClassAudited2 : IFullAuditedObject
{
public DateTime CreationTime { get; set; }
public Guid? CreatorId { get; set; }
public DateTime? LastModificationTime { get; set; }
public Guid? LastModifierId { get; set; }
public bool IsDeleted { get; set; }
public DateTime? DeletionTime { get; set; }
public Guid? DeleterId { get; set; }
}
public class SimpleClassAuditedWithUser1 : IFullAuditedObject<SimpleUser>
{
public DateTime CreationTime { get; set; }
public Guid? CreatorId { get; set; }
public DateTime? LastModificationTime { get; set; }
public Guid? LastModifierId { get; set; }
public bool IsDeleted { get; set; }
public DateTime? DeletionTime { get; set; }
public Guid? DeleterId { get; set; }
public SimpleUser Creator { get; set; }
public SimpleUser LastModifier { get; set; }
public SimpleUser Deleter { get; set; }
}
public class SimpleClassAuditedWithUser2 : IFullAuditedObject<SimpleUser>
{
public DateTime CreationTime { get; set; }
public Guid? CreatorId { get; set; }
public DateTime? LastModificationTime { get; set; }
public Guid? LastModifierId { get; set; }
public bool IsDeleted { get; set; }
public DateTime? DeletionTime { get; set; }
public Guid? DeleterId { get; set; }
public SimpleUser Creator { get; set; }
public SimpleUser LastModifier { get; set; }
public SimpleUser Deleter { get; set; }
}
public class SimpleUser
{
}
}

19
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs

@ -0,0 +1,19 @@
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.AutoMapper;
[DependsOn(
typeof(AbpLuckyPennyAutoMapperModule),
typeof(AbpObjectExtendingTestModule)
)]
public class AutoMapperTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<AutoMapperTestModule>();
});
}
}

70
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_ConfigurationValidation_Tests.cs

@ -0,0 +1,70 @@
using AutoMapper;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
using Xunit;
using IObjectMapper = Volo.Abp.ObjectMapping.IObjectMapper;
namespace Volo.Abp.AutoMapper;
public class AutoMapper_ConfigurationValidation_Tests : AbpIntegratedTest<AutoMapper_ConfigurationValidation_Tests.TestModule>
{
private readonly IObjectMapper _objectMapper;
public AutoMapper_ConfigurationValidation_Tests()
{
_objectMapper = ServiceProvider.GetRequiredService<IObjectMapper>();
}
[Fact]
public void Should_Validate_Configuration()
{
_objectMapper.Map<MySourceClass, MyClassValidated>(new MySourceClass { Value = "42" }).Value.ShouldBe("42");
_objectMapper.Map<MySourceClass, MyClassNonValidated>(new MySourceClass { Value = "42" }).ValueNotMatched.ShouldBe(null);
}
[DependsOn(typeof(AbpLuckyPennyAutoMapperModule))]
public class TestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<TestModule>(validate: true); //Adds all profiles in the TestModule assembly by validating configurations
options.ValidateProfile<NonValidatedProfile>(validate: false); //Exclude a profile from the configuration validation
});
}
}
public class ValidatedProfile : Profile
{
public ValidatedProfile()
{
CreateMap<MySourceClass, MyClassValidated>();
}
}
public class NonValidatedProfile : Profile
{
public NonValidatedProfile()
{
CreateMap<MySourceClass, MyClassNonValidated>();
}
}
public class MySourceClass
{
public string Value { get; set; }
}
public class MyClassValidated
{
public string Value { get; set; }
}
public class MyClassNonValidated
{
public string ValueNotMatched { get; set; }
}
}

87
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_CustomServiceConstruction_Tests.cs

@ -0,0 +1,87 @@
using System;
using AutoMapper;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
using Xunit;
using IObjectMapper = Volo.Abp.ObjectMapping.IObjectMapper;
namespace Volo.Abp.AutoMapper;
public class AutoMapper_CustomServiceConstruction_Tests : AbpIntegratedTest<AutoMapper_CustomServiceConstruction_Tests.TestModule>
{
private readonly IObjectMapper _objectMapper;
public AutoMapper_CustomServiceConstruction_Tests()
{
_objectMapper = ServiceProvider.GetRequiredService<IObjectMapper>();
}
[Fact]
public void Should_Custom_Service_Construction()
{
var source = new SourceModel
{
Name = nameof(SourceModel)
};
_objectMapper.Map<SourceModel, DestModel>(source).Name.ShouldBe(nameof(CustomMappingAction));
}
[DependsOn(typeof(AbpLuckyPennyAutoMapperModule))]
public class TestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Replace the build-in IMapper with a custom one to use ConstructServicesUsing.
context.Services.Replace(ServiceDescriptor.Transient<IMapper>(sp => sp.GetRequiredService<IConfigurationProvider>().CreateMapper()));
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<TestModule>();
options.Configurators.Add(configurationContext =>
{
configurationContext.MapperConfiguration.ConstructServicesUsing(type =>
type.Name.Contains(nameof(CustomMappingAction))
? new CustomMappingAction(nameof(CustomMappingAction))
: Activator.CreateInstance(type));
});
});
}
}
public class SourceModel
{
public string Name { get; set; }
}
public class DestModel
{
public string Name { get; set; }
}
public class MapperActionProfile : Profile
{
public MapperActionProfile()
{
CreateMap<SourceModel, DestModel>().AfterMap<CustomMappingAction>();
}
}
public class CustomMappingAction : IMappingAction<SourceModel, DestModel>
{
private readonly string _name;
public CustomMappingAction(string name)
{
_name = name;
}
public void Process(SourceModel source, DestModel destination, ResolutionContext context)
{
destination.Name = _name;
}
}
}

83
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapper_Dependency_Injection_Tests.cs

@ -0,0 +1,83 @@
using System;
using AutoMapper;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Testing;
using Xunit;
using IObjectMapper = Volo.Abp.ObjectMapping.IObjectMapper;
namespace Volo.Abp.AutoMapper;
public class AutoMapper_Dependency_Injection_Tests : AbpIntegratedTest<AutoMapperTestModule>
{
[Fact]
public void Should_Registered_AutoMapper_Service()
{
GetService<CustomMappingAction>().ShouldNotBeNull();
}
[Fact]
public void Custom_MappingAction_Test()
{
var sourceModel = new SourceModel
{
Name = "Source"
};
using (var scope = ServiceProvider.CreateScope())
{
scope.ServiceProvider.GetRequiredService<IObjectMapper>().Map<SourceModel, DestModel>(sourceModel).Name.ShouldBe(nameof(CustomMappingActionService));
}
CustomMappingAction.IsDisposed.ShouldBeTrue();
}
public class SourceModel
{
public string Name { get; set; }
}
public class DestModel
{
public string Name { get; set; }
}
public class MapperActionProfile : Profile
{
public MapperActionProfile()
{
CreateMap<SourceModel, DestModel>().AfterMap<CustomMappingAction>();
}
}
public class CustomMappingAction : IMappingAction<SourceModel, DestModel>, IDisposable
{
public static bool IsDisposed = false;
private readonly CustomMappingActionService _customMappingActionService;
public CustomMappingAction(CustomMappingActionService customMappingActionService)
{
_customMappingActionService = customMappingActionService;
}
public void Process(SourceModel source, DestModel destination, ResolutionContext context)
{
destination.Name = _customMappingActionService.GetName();
}
public void Dispose()
{
IsDisposed = true;
}
}
public class CustomMappingActionService : ITransientDependency
{
public string GetName()
{
return nameof(CustomMappingActionService);
}
}
}

32
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/ObjectMapperExtensions_Tests.cs

@ -0,0 +1,32 @@
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using System;
using Volo.Abp.AutoMapper.SampleClasses;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.AutoMapper;
public class ObjectMapperExtensions_Tests : AbpIntegratedTest<AutoMapperTestModule>
{
private readonly IObjectMapper _objectMapper;
public ObjectMapperExtensions_Tests()
{
_objectMapper = ServiceProvider.GetRequiredService<IObjectMapper>();
}
[Fact]
public void Should_Map_Objects_With_AutoMap_Attributes()
{
var dto = _objectMapper.Map<MyEntity, MyEntityDto>(
new MyEntity
{
Number = 42
}
);
dto.As<MyEntityDto>().Number.ShouldBe(42);
}
}

10
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntity.cs

@ -0,0 +1,10 @@
using System;
namespace Volo.Abp.AutoMapper.SampleClasses;
public class MyEntity
{
public Guid Id { get; set; }
public int Number { get; set; }
}

10
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntityDto.cs

@ -0,0 +1,10 @@
using System;
namespace Volo.Abp.AutoMapper.SampleClasses;
public class MyEntityDto
{
public Guid Id { get; set; }
public int Number { get; set; }
}

10
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntityDto2.cs

@ -0,0 +1,10 @@
using System;
namespace Volo.Abp.AutoMapper.SampleClasses;
public class MyEntityDto2
{
public Guid Id { get; set; }
public int Number { get; set; }
}

43
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntityDtoWithMappingMethods.cs

@ -0,0 +1,43 @@
using System;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.AutoMapper.SampleClasses;
//TODO: Move tests to Volo.Abp.ObjectMapping test project
public class MyEntityDtoWithMappingMethods : IMapFrom<MyEntity>, IMapTo<MyEntity>
{
public Guid Key { get; set; }
public int No { get; set; }
public MyEntityDtoWithMappingMethods()
{
}
public MyEntityDtoWithMappingMethods(MyEntity entity)
{
MapFrom(entity);
}
public void MapFrom(MyEntity source)
{
Key = source.Id;
No = source.Number;
}
MyEntity IMapTo<MyEntity>.MapTo()
{
return new MyEntity
{
Id = Key,
Number = No
};
}
void IMapTo<MyEntity>.MapTo(MyEntity destination)
{
destination.Id = Key;
destination.Number = No;
}
}

39
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEntityToMyEntityDto2Mapper.cs

@ -0,0 +1,39 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.AutoMapper.SampleClasses;
public class MyEntityToMyEntityDto2Mapper : IObjectMapper<MyEntity, MyEntityDto2>, IObjectMapper<MyEntityDto2, MyEntity>, ITransientDependency
{
public MyEntityDto2 Map(MyEntity source)
{
return new MyEntityDto2
{
Id = source.Id,
Number = source.Number + 1
};
}
public MyEntityDto2 Map(MyEntity source, MyEntityDto2 destination)
{
destination.Id = source.Id;
destination.Number = source.Number + 1;
return destination;
}
public MyEntity Map(MyEntityDto2 source)
{
return new MyEntity
{
Id = source.Id,
Number = source.Number + 1
};
}
public MyEntity Map(MyEntityDto2 source, MyEntity destination)
{
destination.Id = source.Id;
destination.Number = source.Number + 1;
return destination;
}
}

8
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnum.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.AutoMapper.SampleClasses;
public enum MyEnum
{
Value1 = 1,
Value2,
Value3
}

8
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyEnumDto.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.AutoMapper.SampleClasses;
public enum MyEnumDto
{
Value1 = 2,
Value2,
Value3
}

23
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs

@ -0,0 +1,23 @@
using AutoMapper;
using Volo.Abp.ObjectExtending.TestObjects;
namespace Volo.Abp.AutoMapper.SampleClasses;
public class MyMapProfile : Profile
{
public MyMapProfile()
{
CreateMap<MyEntity, MyEntityDto>().ReverseMap();
CreateMap<MyEnum, MyEnumDto>().ReverseMap();
CreateMap<ExtensibleTestPerson, ExtensibleTestPersonDto>()
.MapExtraProperties(ignoredProperties: new[] { "CityName" });
CreateMap<ExtensibleTestPerson, ExtensibleTestPersonWithRegularPropertiesDto>()
.ForMember(x => x.Name, y => y.Ignore())
.ForMember(x => x.Age, y => y.Ignore())
.ForMember(x => x.IsActive, y => y.Ignore())
.MapExtraProperties(mapToRegularProperties: true);
}
}

10
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyNotMappedDto.cs

@ -0,0 +1,10 @@
using System;
namespace Volo.Abp.AutoMapper.SampleClasses;
public class MyNotMappedDto
{
public Guid Id { get; set; }
public int Number { get; set; }
}

34
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/AbpLuckyPennyMultiLingualObjectsTestModule.cs

@ -0,0 +1,34 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Autofac;
using Volo.Abp.AutoMapper;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings;
namespace Volo.Abp.MultiLingualObjects;
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpLocalizationModule),
typeof(AbpSettingsModule),
typeof(AbpObjectMappingModule),
typeof(AbpMultiLingualObjectsModule),
typeof(AbpTestBaseModule),
typeof(AbpLuckyPennyAutoMapperModule)
)]
public class AbpLuckyPennyMultiLingualObjectsTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpSettingOptions>(options =>
{
options.DefinitionProviders.Add<LocalizationSettingProvider>();
});
context.Services.AddAutoMapperObjectMapper<AbpLuckyPennyMultiLingualObjectsTestModule>();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<MultiLingualObjectTestProfile>(validate: true);
});
}
}

156
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs

@ -0,0 +1,156 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.AutoMapper;
using Volo.Abp.Localization;
using Volo.Abp.MultiLingualObjects.TestObjects;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.MultiLingualObjects;
public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpLuckyPennyMultiLingualObjectsTestModule>
{
private readonly IMultiLingualObjectManager _multiLingualObjectManager;
private readonly MultiLingualBook _book;
private readonly List<MultiLingualBook> _books;
private readonly IMapperAccessor _mapperAccessor;
private readonly FrozenDictionary<string, string> _testTranslations = new Dictionary<string, string>
{
["ar"] = "C# التعمق في",
["zh-Hans"] = "深入理解C#",
["en"] = "C# in Depth"
}.ToFrozenDictionary();
public MultiLingualObjectManager_Tests()
{
_multiLingualObjectManager = ServiceProvider.GetRequiredService<IMultiLingualObjectManager>();
//Single Lookup
_book = GetTestBook("en", "zh-Hans");
//Bulk lookup
_books = new List<MultiLingualBook>
{
//has no translations
GetTestBook(),
//english only
GetTestBook("en"),
//arabic only
GetTestBook("ar"),
//arabic + english
GetTestBook("en","ar"),
//arabic + english + chineese
GetTestBook("en", "ar", "zh-Hans")
};
_mapperAccessor = ServiceProvider.GetRequiredService<IMapperAccessor>();
}
MultiLingualBook GetTestBook(params string[] included)
{
var id = Guid.NewGuid();
var res = new MultiLingualBook(id, 100);
foreach (var language in included)
{
res.Translations.Add(new MultiLingualBookTranslation
{
Language = language,
Name = _testTranslations[language],
});
}
return res;
}
[Fact]
public async Task GetTranslationAsync()
{
using (CultureHelper.Use("en-us"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book);
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
[Fact]
public async Task GetTranslationFromListAsync()
{
using (CultureHelper.Use("en-us"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync(_book.Translations);
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
[Fact]
public async Task Should_Get_Specified_Language()
{
using (CultureHelper.Use("zh-Hans"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book, culture: "en");
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
[Fact]
public async Task GetBulkTranslationsAsync()
{
using (CultureHelper.Use("en-us"))
{
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
foreach (var (entity, translation) in translations)
{
if (entity.Translations.Any(x => x.Language == "en"))
{
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
else
{
translation.ShouldBeNull();
}
}
}
}
[Fact]
public async Task GetBulkTranslationsFromListAsync()
{
using (CultureHelper.Use("en-us"))
{
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books.Select(x => x.Translations));
foreach (var translation in translations)
{
translation?.Name.ShouldBe(_testTranslations["en"]);
}
}
}
[Fact]
public async Task TestBulkMapping()
{
using (CultureHelper.Use("en-us"))
{
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation);
var mapped = _mapperAccessor.Mapper.Map<List<MultiLingualBook>, List<MultiLingualBookDto>>(_books, options =>
{
options.Items.Add(nameof(MultiLingualBookTranslation), translationsDict);
});
Assert.Equal(mapped.Count, _books.Count);
for (int i = 0; i < mapped.Count; i++)
{
var og = _books[i];
var m = mapped[i];
Assert.Equal(og.Translations.FirstOrDefault(x => x.Language == "en")?.Name, m.Name);
}
}
}
}

23
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs

@ -0,0 +1,23 @@
namespace Volo.Abp.MultiLingualObjects;
using System;
using System.Collections.Generic;
using global::AutoMapper;
using Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualObjectTestProfile : Profile
{
public MultiLingualObjectTestProfile()
{
CreateMap<MultiLingualBook, MultiLingualBookDto>()
.ForMember(x => x.Name,
x => x.MapFrom((src, target, member, context) =>
{
if (context.Items.TryGetValue(nameof(MultiLingualBookTranslation), out var translationsRaw) && translationsRaw is IReadOnlyDictionary<Guid, MultiLingualBookTranslation> translations)
{
return translations.GetValueOrDefault(src.Id)?.Name;
}
return null;
}));
}
}

19
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBook : IMultiLingualObject<MultiLingualBookTranslation>
{
public MultiLingualBook(Guid id, decimal price)
{
Id = id;
Price = price;
}
public Guid Id { get; }
public decimal Price { get; set; }
public ICollection<MultiLingualBookTranslation> Translations { get; set; } = new List<MultiLingualBookTranslation>();
}

12
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs

@ -0,0 +1,12 @@
using System;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookDto
{
public Guid Id { get; set; }
public string? Name { get; set; }
public decimal Price { get; set; }
}

8
framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookTranslation : IObjectTranslation
{
public string? Name { get; set; }
public required string Language { get; set; }
}

1
nupkg/common.ps1

@ -142,6 +142,7 @@ $projects = (
"framework/src/Volo.Abp.Autofac",
"framework/src/Volo.Abp.Autofac.WebAssembly",
"framework/src/Volo.Abp.AutoMapper",
"framework/src/Volo.Abp.LuckyPenny.AutoMapper",
"framework/src/Volo.Abp.AzureServiceBus",
"framework/src/Volo.Abp.BackgroundJobs.Abstractions",
"framework/src/Volo.Abp.BackgroundJobs",

Loading…
Cancel
Save