mirror of https://github.com/abpframework/abp.git
50 changed files with 2151 additions and 0 deletions
@ -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. |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,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>>() |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
[assembly: InternalsVisibleTo("Volo.Abp.LuckyPenny.AutoMapper.Tests")] |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -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 |
|||
} |
|||
] |
|||
} |
|||
@ -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> |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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>()); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using AutoMapper; |
|||
|
|||
namespace Volo.Abp.AutoMapper; |
|||
|
|||
public interface IAbpAutoMapperConfigurationContext |
|||
{ |
|||
IMapperConfigurationExpression MapperConfiguration { get; } |
|||
|
|||
IServiceProvider ServiceProvider { get; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using AutoMapper; |
|||
|
|||
namespace Volo.Abp.AutoMapper; |
|||
|
|||
public interface IMapperAccessor |
|||
{ |
|||
IMapper Mapper { get; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using AutoMapper; |
|||
|
|||
namespace Volo.Abp.AutoMapper; |
|||
|
|||
internal class MapperAccessor : IMapperAccessor |
|||
{ |
|||
public IMapper Mapper { get; set; } = default!; |
|||
} |
|||
@ -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}"); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "test.framework" |
|||
} |
|||
@ -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> |
|||
@ -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}); |
|||
}); |
|||
}*/ |
|||
} |
|||
@ -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>(); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -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>(); |
|||
}); |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.AutoMapper.SampleClasses; |
|||
|
|||
public class MyEntity |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public int Number { get; set; } |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.AutoMapper.SampleClasses; |
|||
|
|||
public class MyEntityDto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public int Number { get; set; } |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.AutoMapper.SampleClasses; |
|||
|
|||
public class MyEntityDto2 |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public int Number { get; set; } |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.AutoMapper.SampleClasses; |
|||
|
|||
public enum MyEnum |
|||
{ |
|||
Value1 = 1, |
|||
Value2, |
|||
Value3 |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.AutoMapper.SampleClasses; |
|||
|
|||
public enum MyEnumDto |
|||
{ |
|||
Value1 = 2, |
|||
Value2, |
|||
Value3 |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.AutoMapper.SampleClasses; |
|||
|
|||
public class MyNotMappedDto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public int Number { get; set; } |
|||
} |
|||
@ -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); |
|||
}); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
})); |
|||
} |
|||
} |
|||
@ -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>(); |
|||
} |
|||
@ -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; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Volo.Abp.MultiLingualObjects.TestObjects; |
|||
|
|||
public class MultiLingualBookTranslation : IObjectTranslation |
|||
{ |
|||
public string? Name { get; set; } |
|||
|
|||
public required string Language { get; set; } |
|||
} |
|||
Loading…
Reference in new issue