Skip to content

Commit

Permalink
Merge pull request #545 from stormaref/master
Browse files Browse the repository at this point in the history
Fix Issues #536 -> Set properties with reflection if destination has init only properties.
  • Loading branch information
andrerav authored Feb 21, 2023
2 parents 65b6466 + 845d112 commit 0e31a0b
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/Mapster.Tool.Tests/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"mapster.tool": {
"version": "8.3.0",
"commands": [
"dotnet-mapster"
]
}
}
}
11 changes: 11 additions & 0 deletions src/Mapster.Tool.Tests/Mappers/IUserMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Linq.Expressions;

namespace Mapster.Tool.Tests.Mappers;

[Mapper]
public interface IUserMapper
{
Expression<Func<_User, _UserDto>> UserProjection { get; }
_UserDto MapTo(_User user);
_UserDto MapTo(_User user, _UserDto userDto);
}
37 changes: 37 additions & 0 deletions src/Mapster.Tool.Tests/Mappers/UserMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Linq.Expressions;
using Mapster.Tool.Tests;
using Mapster.Tool.Tests.Mappers;

namespace Mapster.Tool.Tests.Mappers
{
public partial class UserMapper : IUserMapper
{
public Expression<Func<_User, _UserDto>> UserProjection => p1 => new _UserDto()
{
Id = p1.Id,
Name = p1.Name
};
public _UserDto MapTo(_User p2)
{
return p2 == null ? null : new _UserDto()
{
Id = p2.Id,
Name = p2.Name
};
}
public _UserDto MapTo(_User p3, _UserDto p4)
{
if (p3 == null)
{
return null;
}
_UserDto result = p4 ?? new _UserDto();

typeof(_UserDto).GetProperty("Id").SetValue(result, (object)p3.Id);
typeof(_UserDto).GetProperty("Name").SetValue(result, p3.Name);
return result;

}
}
}
52 changes: 52 additions & 0 deletions src/Mapster.Tool.Tests/Mapster.Tool.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Scrutor" Version="4.2.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Generated Include="**\*.g.cs" />
<Generated Remove="obj\**" />
</ItemGroup>
<Target Name="CleanGenerated">
<Delete Files="@(Generated)" />
</Target>
<Target Name="Mapster">
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet build" />
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet tool restore" />
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster model -a $(TargetDir)$(ProjectName).dll -n Sample.CodeGen.Models -o Models -r" />
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster extension -a $(TargetDir)$(ProjectName).dll -n Sample.CodeGen.Models -o Models" />
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster mapper -a $(TargetDir)$(ProjectName).dll -n Sample.CodeGen.Mappers -o Mappers" />
</Target>
<ItemGroup>
<Compile Remove="obj\**" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Remove="obj\**" />
</ItemGroup>
<ItemGroup>
<None Remove="obj\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Mapster.Tool\Mapster.Tool.csproj" />
</ItemGroup>

</Project>
38 changes: 38 additions & 0 deletions src/Mapster.Tool.Tests/TestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.Extensions.DependencyInjection;

namespace Mapster.Tool.Tests;

public class TestBase
{
private readonly IServiceScopeFactory _scopeFactory;

public TestBase()
{
var services = ConfigureServiceCollection();
using var scope = services.BuildServiceProvider().CreateScope();
_scopeFactory = scope.ServiceProvider.GetRequiredService<IServiceScopeFactory>();
}

private ServiceCollection ConfigureServiceCollection()
{
ServiceCollection services = new();
services.Scan(selector => selector
.FromCallingAssembly()
.AddClasses()
.AsMatchingInterface()
.WithSingletonLifetime());
return services;
}

protected TInterface GetMappingInterface<TInterface>()
{
using var scope = _scopeFactory.CreateScope();
var service = scope.ServiceProvider.GetService(typeof(TInterface));
if (service == null)
{
throw new Exception($"Service of type {typeof(TInterface).Name} not found!");
}

return (TInterface)service;
}
}
1 change: 1 addition & 0 deletions src/Mapster.Tool.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Reflection;
using FluentAssertions;
using Mapster.Tool.Tests.Mappers;

namespace Mapster.Tool.Tests;

/// <summary>
/// Tests for https:/MapsterMapper/Mapster/issues/536
/// </summary>
public class WhenMappingWithExistingObjectAndInitProperties : TestBase
{
[Fact]
public void MapWithReflection()
{
TypeAdapterConfig.GlobalSettings
.Scan(Assembly.GetExecutingAssembly());

var userMapper = GetMappingInterface<IUserMapper>();
var expected = "Aref";
var user = new _User { Name = expected, Id = 1 };
var dto = new _UserDto();
userMapper.MapTo(user, dto);
dto.Name.Should().Be(expected);
}
}

public class UserMappingRegister : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.NewConfig<_User, _UserDto>()
.MapToConstructor(true)
.ConstructUsing(s => new _UserDto());
}
}

public class _User
{
public int Id { get; init; }
public string Name { get; init; }
}

public class _UserDto
{
public int Id { get; init; }
public string Name { get; init; }
}
7 changes: 7 additions & 0 deletions src/Mapster.sln
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExpressionTranslator", "Exp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemplateTest", "TemplateTest\TemplateTest.csproj", "{ED390145-FA22-46BA-86A6-9FA6AC869BA4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mapster.Tool.Tests", "Mapster.Tool.Tests\Mapster.Tool.Tests.csproj", "{E64E9CEB-8FB2-4012-BBA8-4C2B99FD54C1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -155,6 +157,10 @@ Global
{ED390145-FA22-46BA-86A6-9FA6AC869BA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED390145-FA22-46BA-86A6-9FA6AC869BA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED390145-FA22-46BA-86A6-9FA6AC869BA4}.Release|Any CPU.Build.0 = Release|Any CPU
{E64E9CEB-8FB2-4012-BBA8-4C2B99FD54C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E64E9CEB-8FB2-4012-BBA8-4C2B99FD54C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E64E9CEB-8FB2-4012-BBA8-4C2B99FD54C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E64E9CEB-8FB2-4012-BBA8-4C2B99FD54C1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -175,6 +181,7 @@ Global
{D5DF0FB7-44A5-4326-9EC4-5B8F7FCCE00F} = {D33E5A90-ABCA-4B2D-8758-2873CC5AB847}
{3CB56440-5449-4DE5-A8D3-549C87C1B36A} = {EF7E343F-592E-4EAC-A0A4-92EB4B95CB89}
{ED390145-FA22-46BA-86A6-9FA6AC869BA4} = {D33E5A90-ABCA-4B2D-8758-2873CC5AB847}
{E64E9CEB-8FB2-4012-BBA8-4C2B99FD54C1} = {D33E5A90-ABCA-4B2D-8758-2873CC5AB847}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {83B87DBA-277C-49F1-B597-E3B78C2C8275}
Expand Down
29 changes: 28 additions & 1 deletion src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,18 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
}
else
{
adapt = member.DestinationMember.SetExpression(destination, adapt);
//Todo Try catch block should be removed after pull request approved
try
{
var destinationPropertyInfo = (PropertyInfo)member.DestinationMember.Info!;
adapt = destinationPropertyInfo.IsInitOnly()
? SetValueByReflection(destination, (MemberExpression)adapt, arg.DestinationType)
: member.DestinationMember.SetExpression(destination, adapt);
}
catch (Exception e)
{
adapt = member.DestinationMember.SetExpression(destination, adapt);
}
}
}
else if (!adapt.IsComplex())
Expand All @@ -146,6 +157,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
tuple = Tuple.Create(new List<Expression>(), body);
conditions[member.Ignore.Condition] = tuple;
}

tuple.Item1.Add(adapt);
}
else
Expand All @@ -166,6 +178,21 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
return lines.Count > 0 ? (Expression)Expression.Block(lines) : Expression.Empty();
}

private static Expression SetValueByReflection(Expression destination, MemberExpression adapt,
Type destinationType)
{
var memberName = adapt.Member.Name;
var typeofExpression = Expression.Constant(destinationType);
var getPropertyMethod = typeof(Type).GetMethod("GetProperty", new[] { typeof(string) })!;
var getPropertyExpression = Expression.Call(typeofExpression, getPropertyMethod,
Expression.Constant(memberName));
var setValueMethod =
typeof(PropertyInfo).GetMethod("SetValue", new[] { typeof(object), typeof(object) })!;
var memberAsObject = adapt.To(typeof(object));
return Expression.Call(getPropertyExpression, setValueMethod,
new[] { destination, memberAsObject });
}

protected override Expression? CreateInlineExpression(Expression source, CompileArgument arg)
{
//new TDestination {
Expand Down
10 changes: 10 additions & 0 deletions src/Mapster/Utils/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -374,5 +374,15 @@ public static IEnumerable<Type> GetAllTypes(this Type type)
type = type.GetTypeInfo().BaseType;
} while (type != null && type != typeof(object));
}

public static bool IsInitOnly(this PropertyInfo propertyInfo)
{
var setMethod = propertyInfo.SetMethod;
if (setMethod == null)
return false;

var isExternalInitType = typeof(System.Runtime.CompilerServices.IsExternalInit);
return setMethod.ReturnParameter.GetRequiredCustomModifiers().Contains(isExternalInitType);
}
}
}

0 comments on commit 0e31a0b

Please sign in to comment.