diff --git a/src/AutoMapper/ApiCompatBaseline.txt b/src/AutoMapper/ApiCompatBaseline.txt index ee6b7066f2..125a1cf915 100644 --- a/src/AutoMapper/ApiCompatBaseline.txt +++ b/src/AutoMapper/ApiCompatBaseline.txt @@ -1,5 +1,9 @@ Compat issues with assembly AutoMapper: CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.AutoMapAttribute' changed from '[AttributeUsageAttribute(1036, AllowMultiple=true)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, AllowMultiple=true)]' in the implementation. +InterfacesShouldHaveSameMembers : Interface member 'public TMappingExpression AutoMapper.IMappingExpressionBase.AsProxy()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void AutoMapper.IMappingExpressionBase.AsProxy()' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void AutoMapper.IMappingExpressionBase.AsProxy()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void AutoMapper.Configuration.MappingExpressionBase.AsProxy()' does not exist in the implementation but it does exist in the contract. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.IgnoreAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.MapAtRuntimeAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.MappingOrderAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. @@ -8,4 +12,4 @@ CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMappe CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.UseExistingValueAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.ValueConverterAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.ValueResolverAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. -Total Issues: 9 +Total Issues: 13 diff --git a/src/AutoMapper/Configuration/IMappingExpressionBase.cs b/src/AutoMapper/Configuration/IMappingExpressionBase.cs index 4e3e137051..3c8e4dd0f2 100644 --- a/src/AutoMapper/Configuration/IMappingExpressionBase.cs +++ b/src/AutoMapper/Configuration/IMappingExpressionBase.cs @@ -168,7 +168,8 @@ public interface IMappingExpressionBase /// Create at runtime a proxy type implementing the destination interface. /// - void AsProxy(); + /// Itself + TMappingExpression AsProxy(); /// /// Skip normal member mapping and convert using a instantiated during mapping /// Use this method if you need to specify the converter type at runtime diff --git a/src/AutoMapper/Configuration/MappingExpressionBase.cs b/src/AutoMapper/Configuration/MappingExpressionBase.cs index dc007fb1f3..b52f9c2f34 100644 --- a/src/AutoMapper/Configuration/MappingExpressionBase.cs +++ b/src/AutoMapper/Configuration/MappingExpressionBase.cs @@ -520,13 +520,14 @@ public TMappingExpression IgnoreAllSourcePropertiesWithAnInaccessibleSetter() public void ConvertUsing(Expression> mappingFunction) => TypeMapActions.Add(tm => tm.CustomMapExpression = mappingFunction); - public void AsProxy() + public TMappingExpression AsProxy() { if (!DestinationType.IsInterface) { throw new InvalidOperationException("Only interfaces can be proxied. " + DestinationType); } TypeMapActions.Add(tm => tm.AsProxy = true); + return this as TMappingExpression; } } } \ No newline at end of file diff --git a/src/UnitTests/InterfaceMapping.cs b/src/UnitTests/InterfaceMapping.cs index b102a0b48b..7016266112 100644 --- a/src/UnitTests/InterfaceMapping.cs +++ b/src/UnitTests/InterfaceMapping.cs @@ -309,18 +309,27 @@ public class When_mapping_a_concrete_type_to_an_interface_type : AutoMapperSpecB public class Source { public int Value { get; set; } + public int Value1 { get; set; } } public interface IDestination { int Value { get; set; } + int Value2 { get; set; } + string Value3 { get; set; } } - protected override MapperConfiguration CreateConfiguration() => new(cfg =>cfg.CreateMap().AsProxy()); + protected override MapperConfiguration CreateConfiguration() => new(cfg =>cfg.CreateMap().AsProxy() + .ForMember(x => x.Value2, o => o.MapFrom(x => x.Value1)) + .ForMember(x => x.Value3, o => o.Ignore()) + .AfterMap((_, d) => + { + d.Value3 = "value 3"; + })); protected override void Because_of() { - _result = Mapper.Map(new Source {Value = 5}); + _result = Mapper.Map(new Source {Value = 5, Value1 = 50}); } [Fact] @@ -329,10 +338,110 @@ public void Should_create_an_implementation_of_the_interface() _result.Value.ShouldBe(5); } + [Fact] + public void Should_apply_rules_after_proxying() + { + _result.Value2.ShouldBe(50); + _result.Value3.ShouldBe("value 3"); + } + [Fact] public void Should_not_derive_from_INotifyPropertyChanged() { - _result.ShouldNotBeOfType(); + _result.ShouldNotBeOfType(); + } + } + + public class When_mapping_an_interface_type_to_a_concrete_type_and_reverse : AutoMapperSpecBase + { + public interface ISource + { + int Value { get; set; } + } + + public class Destination + { + public int Value { get; set; } + } + + protected override MapperConfiguration CreateConfiguration() => new(cfg => + cfg.CreateMap().ReverseMap()); + + [Fact] + public void Should_not_convert_to_interface() + { + Should.Throw(() => Mapper.Map(new Destination {Value = 5})) + .Message.ShouldStartWith("Cannot create interface " + typeof(ISource).FullName); + } + } + + public class When_mapping_an_interface_type_to_an_interface_type_and_reverse : AutoMapperSpecBase + { + public interface ISource + { + int Value { get; set; } + } + + public class Source: ISource + { + public int Value { get; set; } + } + + public interface IDestination + { + int Value { get; set; } + } + + protected override MapperConfiguration CreateConfiguration() => new(cfg => + cfg.CreateMap().AsProxy().ReverseMap().AsProxy()); + + [Fact] + public void Should_create_an_implementation_of_the_destination_interface() + { + var destination = Mapper.Map(new Source {Value = 5}); + destination.Value.ShouldBe(5); + } + + [Fact] + public void Should_map_implementation_of_the_interface_to_the_proxied_implementation() + { + var destination = Mapper.Map(new Source {Value = 5}); + var reversed = Mapper.Map(destination); + + reversed.ShouldNotBeOfType(); + reversed.Value.ShouldBe(5); + } + } + + public class When_mapping_a_concrete_type_to_an_interface_type_and_reverse : AutoMapperSpecBase + { + public class Source + { + public int Value { get; set; } + } + + public interface IDestination + { + int Value { get; set; } + } + + protected override MapperConfiguration CreateConfiguration() => new(cfg => + cfg.CreateMap().AsProxy().ReverseMap()); + + [Fact] + public void Should_create_an_implementation_of_the_destination_interface() + { + var destination = Mapper.Map(new Source {Value = 5}); + destination.Value.ShouldBe(5); + } + + [Fact] + public void Should_map_implementation_of_the_interface_to_the_class() + { + var destination = Mapper.Map(new Source {Value = 5}); + var reversed = Mapper.Map(destination); + + reversed.Value.ShouldBe(5); } } @@ -378,7 +487,7 @@ public void Should_notify_property_changes() var count = 0; _result.PropertyChanged += (o, e) => { count++; - o.ShouldBeSameAs(_result); + o.ShouldBeSameAs(_result); e.PropertyName.ShouldBe("Value"); };