Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for internal constructors that are accessible via InternalsVisibleToAttribute #378

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/Mapster.Tests.InternalsVisibleAssembly/DtoStubs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Runtime.CompilerServices;

//Expose this to Mapster.Tests
[assembly: InternalsVisibleTo("Mapster.Tests")]

namespace Mapster.Tests.InternalsVisibleAssembly
{
public class DtoInternal
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; } = -1;
public string Prop { get; set; }
public string OtherProp { get; set; }

internal DtoInternal() { }
}

public class DtoPrivate
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; } = -1;
public string Prop { get; set; }

private DtoPrivate() { }
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

</Project>
3 changes: 2 additions & 1 deletion src/Mapster.Tests/Mapster.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
<AssemblyName>Mapster.Tests</AssemblyName>
<AssemblyOriginatorKeyFile>Mapster.Tests.snk</AssemblyOriginatorKeyFile>
Expand All @@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Mapster\Mapster.csproj" />
<ProjectReference Include="..\Mapster.Tests.InternalsVisibleAssembly\Mapster.Tests.InternalsVisibleAssembly.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="packages.config" />
Expand Down
42 changes: 42 additions & 0 deletions src/Mapster.Tests/WhenMappingInternalConstructor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using Mapster.Tests.InternalsVisibleAssembly;
using System;

namespace Mapster.Tests
{
[TestClass]
public class WhenMappingInternalConstructor
{

[TestMethod]
public void MapToConstructor_InternalVisible()
{
var poco = new Poco { Id = Guid.NewGuid(), Name = "Test", Prop = "Prop", OtherProp = "OtherProp" };
var dto = TypeAdapter.Adapt<DtoInternal>(poco);

dto.Id.ShouldBe(poco.Id);
dto.Name.ShouldBe(poco.Name);
dto.Age.ShouldBe(-1);
dto.Prop.ShouldBe(poco.Prop);
}


[TestMethod]
public void MapToConstructor_PrivateVisible_ShouldThrow()
{
var poco = new Poco { Id = Guid.NewGuid(), Name = "Test", Prop = "Prop", OtherProp = "OtherProp" };
var ex = Assert.ThrowsException<CompileException>(() => poco.Adapt<DtoPrivate>());
Assert.IsInstanceOfType(ex.InnerException, typeof(InvalidOperationException));
Assert.IsTrue(ex.InnerException.Message.Contains("No default constructor for type"));
}

public class Poco
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Prop { get; set; }
public string OtherProp { get; set; }
}
}
}
7 changes: 7 additions & 0 deletions src/Mapster.sln
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mapster.SourceGenerator", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mapster.Core", "Mapster.Core\Mapster.Core.csproj", "{1A7D2FD4-DDEC-4E11-93FF-1310F34F67CF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mapster.Tests.InternalsVisibleAssembly", "Mapster.Tests.InternalsVisibleAssembly\Mapster.Tests.InternalsVisibleAssembly.csproj", "{927CC36B-F45C-4B6E-A55D-D162D06570BD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -137,6 +139,10 @@ Global
{1A7D2FD4-DDEC-4E11-93FF-1310F34F67CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A7D2FD4-DDEC-4E11-93FF-1310F34F67CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A7D2FD4-DDEC-4E11-93FF-1310F34F67CF}.Release|Any CPU.Build.0 = Release|Any CPU
{927CC36B-F45C-4B6E-A55D-D162D06570BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{927CC36B-F45C-4B6E-A55D-D162D06570BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{927CC36B-F45C-4B6E-A55D-D162D06570BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{927CC36B-F45C-4B6E-A55D-D162D06570BD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -156,6 +162,7 @@ Global
{DE045991-6268-46EE-B5D3-79DE75820976} = {916FA044-B9E5-44F2-991A-85AA43C08255}
{D5DF0FB7-44A5-4326-9EC4-5B8F7FCCE00F} = {D33E5A90-ABCA-4B2D-8758-2873CC5AB847}
{3CB56440-5449-4DE5-A8D3-549C87C1B36A} = {EF7E343F-592E-4EAC-A0A4-92EB4B95CB89}
{927CC36B-F45C-4B6E-A55D-D162D06570BD} = {D33E5A90-ABCA-4B2D-8758-2873CC5AB847}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {83B87DBA-277C-49F1-B597-E3B78C2C8275}
Expand Down
40 changes: 38 additions & 2 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Reflection;
using Mapster.Models;
using Mapster.Utils;
using System.Runtime.CompilerServices;

namespace Mapster.Adapters
{
Expand Down Expand Up @@ -360,9 +361,9 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex

//if there is default constructor, use default constructor
else if (arg.DestinationType.HasDefaultConstructor())
{
{
return Expression.New(arg.DestinationType);
}
}

//if mapToTarget or include derived types, allow mapping & throw exception on runtime
//instantiation is not needed
Expand All @@ -382,13 +383,48 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex
return Expression.New(DynamicTypeGenerator.GetTypeForInterface(arg.DestinationType));
}

//if the assembly has an otherwise accessible constructor use it
else if (HasInternalAccessibleDefaultConstructor(arg.DestinationType, arg.SourceType))
{
return Expression.New(arg.DestinationType);
}

//otherwise throw
else
{
throw new InvalidOperationException($"No default constructor for type '{arg.DestinationType.Name}', please use 'ConstructUsing' or 'MapWith'");
}
}



#if !NETSTANDARD1_3
private bool HasInternalAccessibleDefaultConstructor(Type fromType, Type toType)
{

bool internalsVisibleTo() =>
fromType.Assembly.GetCustomAttributes(typeof(InternalsVisibleToAttribute), false)
.Cast<InternalsVisibleToAttribute>()
.Any(x => x.AssemblyName == toType.Assembly.GetName().Name);

bool hasAccessibleConstructor() =>
fromType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)
.Any(c => c.GetParameters().Length == 0 && c.IsAssembly);

return (internalsVisibleTo() && hasAccessibleConstructor());
}
#else
private bool HasInternalAccessibleDefaultConstructor(Type fromType, Type toType)
{
var fromTypeInfo = fromType.GetTypeInfo();
var fromAssembly = fromTypeInfo.Assembly;
var toAssembly = toType.GetTypeInfo().Assembly;
bool internalsVisibleTo() => fromAssembly.GetCustomAttributes<InternalsVisibleToAttribute>().Any(x => x.AssemblyName == toAssembly.GetName().Name);
bool hasAccessibleConstructor() => fromTypeInfo.DeclaredConstructors.Any(x => x.GetParameters().Length == 0 && x.IsAssembly);

return (internalsVisibleTo() && hasAccessibleConstructor());
}
#endif
private static Expression CreateAdaptExpressionCore(Expression source, Type destinationType, CompileArgument arg, MemberMapping? mapping = null, Expression? destination = null)
{
var mapType = arg.MapType == MapType.MapToTarget && destination == null ? MapType.Map :
Expand Down