diff --git a/src/Mapster.Tests.InternalsVisibleAssembly/DtoStubs.cs b/src/Mapster.Tests.InternalsVisibleAssembly/DtoStubs.cs new file mode 100644 index 00000000..b7116739 --- /dev/null +++ b/src/Mapster.Tests.InternalsVisibleAssembly/DtoStubs.cs @@ -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() { } + } +} + diff --git a/src/Mapster.Tests.InternalsVisibleAssembly/Mapster.Tests.InternalsVisibleAssembly.csproj b/src/Mapster.Tests.InternalsVisibleAssembly/Mapster.Tests.InternalsVisibleAssembly.csproj new file mode 100644 index 00000000..86ea3bbe --- /dev/null +++ b/src/Mapster.Tests.InternalsVisibleAssembly/Mapster.Tests.InternalsVisibleAssembly.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp2.1 + + + diff --git a/src/Mapster.Tests/Mapster.Tests.csproj b/src/Mapster.Tests/Mapster.Tests.csproj index 144b5e3b..d8c1e436 100644 --- a/src/Mapster.Tests/Mapster.Tests.csproj +++ b/src/Mapster.Tests/Mapster.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 false Mapster.Tests Mapster.Tests.snk @@ -19,6 +19,7 @@ + diff --git a/src/Mapster.Tests/WhenMappingInternalConstructor.cs b/src/Mapster.Tests/WhenMappingInternalConstructor.cs new file mode 100644 index 00000000..157a399b --- /dev/null +++ b/src/Mapster.Tests/WhenMappingInternalConstructor.cs @@ -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(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(() => poco.Adapt()); + 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; } + } + } +} diff --git a/src/Mapster.sln b/src/Mapster.sln index 2fd8a0cd..567b1913 100644 --- a/src/Mapster.sln +++ b/src/Mapster.sln @@ -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 @@ -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 @@ -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} diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index 0064f889..0908613d 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -5,6 +5,7 @@ using System.Reflection; using Mapster.Models; using Mapster.Utils; +using System.Runtime.CompilerServices; namespace Mapster.Adapters { @@ -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 @@ -382,6 +383,12 @@ 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 { @@ -389,6 +396,35 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex } } + + +#if !NETSTANDARD1_3 + private bool HasInternalAccessibleDefaultConstructor(Type fromType, Type toType) + { + + bool internalsVisibleTo() => + fromType.Assembly.GetCustomAttributes(typeof(InternalsVisibleToAttribute), false) + .Cast() + .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().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 :