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 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`. [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. 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 ## 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: 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.WebAssembly/Volo.Abp.Autofac.WebAssembly.csproj" />
<Project Path="src/Volo.Abp.Autofac/Volo.Abp.Autofac.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.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.AzureServiceBus/Volo.Abp.AzureServiceBus.csproj" />
<Project Path="src/Volo.Abp.BackgroundJobs.Abstractions/Volo.Abp.BackgroundJobs.Abstractions.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" /> <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.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj" />
<Project Path="test/Volo.Abp.Autofac.Tests/Volo.Abp.Autofac.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.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.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.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj" />
<Project Path="test/Volo.Abp.BlobStoring.Aws.Tests/Volo.Abp.BlobStoring.Aws.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",
"framework/src/Volo.Abp.Autofac.WebAssembly", "framework/src/Volo.Abp.Autofac.WebAssembly",
"framework/src/Volo.Abp.AutoMapper", "framework/src/Volo.Abp.AutoMapper",
"framework/src/Volo.Abp.LuckyPenny.AutoMapper",
"framework/src/Volo.Abp.AzureServiceBus", "framework/src/Volo.Abp.AzureServiceBus",
"framework/src/Volo.Abp.BackgroundJobs.Abstractions", "framework/src/Volo.Abp.BackgroundJobs.Abstractions",
"framework/src/Volo.Abp.BackgroundJobs", "framework/src/Volo.Abp.BackgroundJobs",

Loading…
Cancel
Save