Skip to content

Commit

Permalink
- allow map to target without default constructor (sp thx to Burgyn)
Browse files Browse the repository at this point in the history
- throw when pass nest destination member access to Map method
  • Loading branch information
chaowlert committed Nov 11, 2017
1 parent 266667f commit a0f2220
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 25 deletions.
4 changes: 2 additions & 2 deletions src/Mapster.Tests/WhenUsingNonDefaultConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void Map_To_Existing_Destination_Instance_Should_Pass()
}

[TestMethod]
public void Map_To_Destination_Type_Without_Default_Constructor_Shoud_Throw_Argument_Exception()
public void Map_To_Destination_Type_Without_Default_Constructor_Shoud_Throw_Exception()
{
var simplePoco = new SimplePoco { Id = Guid.NewGuid(), Name = "TestName" };

Expand All @@ -83,7 +83,7 @@ public void Map_To_Destination_Type_Without_Default_Constructor_Shoud_Throw_Argu
};

action.ShouldThrow<CompileException>()
.InnerException.ShouldBeOfType<ArgumentException>();
.InnerException.ShouldBeOfType<InvalidOperationException>();
}

#region TestClasses
Expand Down
19 changes: 14 additions & 5 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,21 +272,30 @@ protected Expression CreateInstantiationExpression(Expression source, CompileArg
protected virtual Expression CreateInstantiationExpression(Expression source, Expression destination, CompileArgument arg)
{
//new TDestination()

//if there is constructUsing, use constructUsing
var constructUsing = arg.Settings.ConstructUsingFactory?.Invoke(arg);
Expression newObj;
if (constructUsing != null)
newObj = constructUsing.Apply(source).TrimConversion(true).To(arg.DestinationType);
else if (arg.DestinationType.GetTypeInfo().IsAbstract && arg.Settings.Includes.Count > 0)

//if there is default constructor, use default constructor
else if (arg.DestinationType.HasDefaultConstructor())
newObj = Expression.New(arg.DestinationType);

//if mapToTarget or include derived types, allow mapping & throw exception on runtime
//instantiation is not needed
else if (destination != null || arg.Settings.Includes.Count > 0)
newObj = Expression.Throw(
Expression.New(
// ReSharper disable once AssignNullToNotNullAttribute
typeof(InvalidOperationException).GetConstructor(new[] { typeof(string) }),
Expression.Constant("Cannot instantiate abstract type: " + arg.DestinationType.Name)),
Expression.Constant("Cannot instantiate type: " + arg.DestinationType.Name)),
arg.DestinationType);
else if (destination == null || arg.DestinationType.HasDefaultConstructor())
newObj = Expression.New(arg.DestinationType);

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

//dest ?? new TDest();
return destination == null
Expand Down
13 changes: 11 additions & 2 deletions src/Mapster/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Mapster.Models;

namespace Mapster
Expand All @@ -22,7 +23,7 @@ public static bool HasCustomAttribute(this IMemberModel member, Type type)

public static T GetCustomAttribute<T>(this IMemberModel member)
{
return (T)member.GetCustomAttributes(true).FirstOrDefault(attr => attr is T);
return (T) member.GetCustomAttributes(true).FirstOrDefault(attr => attr is T);
}

/// <summary>
Expand All @@ -33,7 +34,15 @@ public static T GetCustomAttribute<T>(this IMemberModel member)
/// <c>true</c> if specific <paramref name="type"/> has default constructor; otherwise <c>false</c>.
/// </returns>
public static bool HasDefaultConstructor(this Type type)
=> type.IsValueType || type.GetConstructor(Type.EmptyTypes) != null;
{
if (type == typeof(void)
|| type.GetTypeInfo().IsAbstract
|| type.GetTypeInfo().IsInterface)
return false;
if (type.GetTypeInfo().IsValueType)
return true;
return type.GetConstructor(Type.EmptyTypes) != null;
}

}
}
6 changes: 3 additions & 3 deletions src/Mapster/Mapster.NetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
<PackageId>Mapster</PackageId>
<PackageTags>Mapper;AutoMapper;Fast;Mapping</PackageTags>
<PackageIconUrl>https://cloud.githubusercontent.com/assets/5763993/26522718/d16f3e42-4330-11e7-9b78-f8c7402624e7.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/chaowlert/Mapster</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/chaowlert/Mapster/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/MapsterMapper/Mapster</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/MapsterMapper/Mapster/blob/master/LICENSE</PackageLicenseUrl>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.3' ">1.6.0</NetStandardImplicitPackageVersion>
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
<GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute>
Expand All @@ -22,7 +22,7 @@
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>3.1.6</Version>
<Version>3.1.7</Version>
<RootNamespace>Mapster</RootNamespace>
</PropertyGroup>

Expand Down
28 changes: 15 additions & 13 deletions src/Mapster/Utils/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,26 +128,28 @@ public static Type UnwrapNullable(this Type type)
return type.IsNullable() ? type.GetGenericArguments()[0] : type;
}

public static MemberExpression GetMemberInfo(Expression method, bool noThrow = false)
public static MemberExpression GetMemberInfo(Expression member, bool source = false)
{
var lambda = method as LambdaExpression;
var lambda = member as LambdaExpression;
if (lambda == null)
throw new ArgumentNullException(nameof(method));

MemberExpression memberExpr = null;
throw new ArgumentNullException(nameof(member));

var expr = lambda.Body;
if (lambda.Body.NodeType == ExpressionType.Convert)
expr = ((UnaryExpression)lambda.Body).Operand;

MemberExpression memberExpr = null;
if (expr.NodeType == ExpressionType.MemberAccess)
{
memberExpr =
((UnaryExpression) lambda.Body).Operand as MemberExpression;
}
else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
{
memberExpr = lambda.Body as MemberExpression;
var tmp = (MemberExpression) expr;
if (tmp.Expression.NodeType == ExpressionType.Parameter)
memberExpr = tmp;
else if (!source)
throw new ArgumentException("Only first level member access on destination allowed (eg. dest => dest.Name)", nameof(member));
}

if (memberExpr == null && !noThrow)
throw new ArgumentException("argument must be member access", nameof(method));
if (memberExpr == null && !source)
throw new ArgumentException("Argument must be member access", nameof(member));

return memberExpr;
}
Expand Down

3 comments on commit a0f2220

@cougario
Copy link

@cougario cougario commented on a0f2220 Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This throws null reference exception on GetMemberInfo version 3.1.7. Earlier version 3.1.6 works.

config.NewConfig<FileReceive, MainDocument>()
                .Map(dest => dest.FileName, src => src.Name)
                .Map(dest => dest.LastModified, src => DateTime.UtcNow)
                .Map(dest => dest.FileData, src => new FileData { Content = src.Content });

@chaowlert
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I will add unit test and fix soon.

@chaowlert
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 3.1.8

Please sign in to comment.